From aaf8bdbee7fdc1d4721f43307fc824c373c69ec4 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Mon, 10 Jan 2011 23:24:02 -0500 Subject: some improvements to behavior chain. A "Square" Behavior --- LightInstallation.py | 8 ++------ TestAll.py | 4 ++++ behaviors/BehaviorChain.py | 5 +++++ behaviors/Square.py | 11 +++++++++++ config/Outdoor.xml | 9 ++++++++- pixelmappers/SimpleMapper.py | 1 + tests/TestComponentRegistry.py | 12 ++++++++++-- util/ComponentRegistry.py | 10 ++++++++-- 8 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 behaviors/Square.py diff --git a/LightInstallation.py b/LightInstallation.py index aaedcf2..2726005 100644 --- a/LightInstallation.py +++ b/LightInstallation.py @@ -79,12 +79,8 @@ class LightInstallation: self.renderers = self.initializeComponent(rendererConfig) def registerComponents(self, components): for component in components: - cid = component['Id'] - if cid == None: #TODO: determine if componenent is critical, and if so, die - main_log.error('Components must be registered with Ids. Component not registered') - else: - compReg.registerComponent(component) - main_log.debug(cid + ' registered') + cid = compReg.registerComponent(component) + main_log.debug(cid + ' registered') def initializeComponent(self, config): components = [] if config != None: diff --git a/TestAll.py b/TestAll.py index 9921c7e..23b34ea 100644 --- a/TestAll.py +++ b/TestAll.py @@ -1,5 +1,9 @@ import unittest from unittest import TestLoader import tests.TestConfigLoaders +import tests.TestComponentRegistry testSuite = TestLoader().loadTestsFromModule(tests.TestConfigLoaders) unittest.TextTestRunner(verbosity=2).run(testSuite) + +testSuite = TestLoader().loadTestsFromModule(tests.TestComponentRegistry) +unittest.TextTestRunner(verbosity=2).run(testSuite) diff --git a/behaviors/BehaviorChain.py b/behaviors/BehaviorChain.py index 0170fa8..e15bd23 100644 --- a/behaviors/BehaviorChain.py +++ b/behaviors/BehaviorChain.py @@ -32,3 +32,8 @@ yourself or bug russell') self.feedback[behaviorId] = recurrence return response + def appendBehavior(behavior): + bid = compReg.registerComponent(behavior) #register behavior (will make + #a new id if there isn't one) + self['ChainedBehaviors'].append(bid) + diff --git a/behaviors/Square.py b/behaviors/Square.py new file mode 100644 index 0000000..a6e9401 --- /dev/null +++ b/behaviors/Square.py @@ -0,0 +1,11 @@ +from operationscore.Behavior import * +class Square(Behavior): + def processResponse(self, sensorInputs, recursiveInputs): + for sensory in sensorInputs:#TODO: consider replicating the dict + xLoc = sensory['Location'][0] + yLoc = sensory['Location'][1] + width = self['Width'] + sensory['Location'] =\ + '{x}<'+str(xLoc+width)+',{x}>'+str(xLoc-width)+\ + ',{y}<'+str(yLoc+width)+',{y}>'+str(yLoc-width) + return (sensorInputs, recursiveInputs) diff --git a/config/Outdoor.xml b/config/Outdoor.xml index f0995b1..053f31e 100644 --- a/config/Outdoor.xml +++ b/config/Outdoor.xml @@ -84,6 +84,13 @@ pixelsleft + + behaviors.Square + + square + 10 + + behaviors/LoopAndDie.xml @@ -130,7 +137,7 @@ echo - pixelsleft + square decay True diff --git a/pixelmappers/SimpleMapper.py b/pixelmappers/SimpleMapper.py index 2df24e0..31a48cc 100644 --- a/pixelmappers/SimpleMapper.py +++ b/pixelmappers/SimpleMapper.py @@ -32,6 +32,7 @@ class SimpleMapper(PixelMapper): if pixelValid: ret.append((pixel, 1)) except Exception as exp: + pdb.set_trace() raise Exception('Bad event condition') return ret diff --git a/tests/TestComponentRegistry.py b/tests/TestComponentRegistry.py index 1acd0c8..5ada524 100644 --- a/tests/TestComponentRegistry.py +++ b/tests/TestComponentRegistry.py @@ -1,11 +1,19 @@ import unittest import util.ComponentRegistry as compReg +from operationscore.SmootCoreObject import SmootCoreObject class TestComponentRegistry(unittest.TestCase): def setUp(self): compReg.initRegistry() def tearDown(self): compReg.clearRegistry() - def test_register_component(self): - comp = SmootCoreObject({'Id': obj1}) + def test_register_component_id_specified(self): + comp = SmootCoreObject({'Id': 'obj1'}) compReg.registerComponent(comp) + newcomp = compReg.getComponent('obj1') + assert comp == newcomp + def test_register_new_id(self): + comp = SmootCoreObject({}) + cid =compReg.registerComponent(comp) + newcomp = compReg.getComponent(cid) + assert comp == newcomp diff --git a/util/ComponentRegistry.py b/util/ComponentRegistry.py index 0518f56..43c4795 100644 --- a/util/ComponentRegistry.py +++ b/util/ComponentRegistry.py @@ -4,14 +4,18 @@ from logger import main_log #TODO: make component registry a singleton def initRegistry(): #TODO: don't overwrite existing registry - globals()['Registry'] = {} + if not 'Registry' in globals(): + globals()['Registry'] = {} def clearRegistry(): initRegistry() + def removeComponent(cid): globals()['Registry'].pop(cid) + def getComponent(cid): return globals()['Registry'][cid] + #Registry of all components of the light system #TODO: pick a graceful failure behavior and implement it def registerComponent(component, cid=None): @@ -26,11 +30,13 @@ def registerComponent(component, cid=None): main_log.debug(cid + 'automatically assigned') globals()['Registry'][cid] = component return cid -#def registerDefault( + def removeComponent(cid): globals()['Registry'].pop(cid) + def getComponent(cid): return globals()['Registry'][cid] + def getNewId(): trialKey = len(globals()['Registry']) trialId = hashlib.md5(str(trialKey)).hexdigest() -- cgit v1.2.3 From 907780f6b298bfa259ace94d07ca137099067e67 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Wed, 12 Jan 2011 11:58:00 -0500 Subject: Added ResponseMover to facilitate spawn and run responses. Modified simplemapper to fix a bug with null responses. --- ResponseMover.py | 15 --------------- behaviors/PixelDecay.xml | 9 --------- behaviors/RandomWalk.py | 13 +++++++++++++ behaviors/ResponseMover.py | 15 +++++++++++++++ config/Outdoor.xml | 36 ++++++++++++++++++++++++++++++++++++ ga | 0 pixelcore/Screen.py | 3 +++ pixelmappers/SimpleMapper.py | 5 ++++- util/Config.py | 1 + 9 files changed, 72 insertions(+), 25 deletions(-) delete mode 100644 ResponseMover.py create mode 100644 behaviors/ResponseMover.py mode change 100644 => 100755 ga diff --git a/ResponseMover.py b/ResponseMover.py deleted file mode 100644 index 718400d..0000000 --- a/ResponseMover.py +++ /dev/null @@ -1,15 +0,0 @@ -from operationscore.Behavior import * -import util.ComponentRegistry as compReg -#ResponseMover is a scaffold for behaviors that spawn 'walkers' which act autonomously on input. -#Add a recursive hook to control the movement. -class ResponseMover(Behavior): - def processResponse(self, sensorInputs, recursiveInputs): - newResponses = sensorInputs - ret = [] - ret += newResponses - for recurInput in recursiveInputs: - outDict = dict(recurInput) - ret.append(outDict) - ret += newResponses - return (ret, ret) - diff --git a/behaviors/PixelDecay.xml b/behaviors/PixelDecay.xml index bfe84ca..c19a1a8 100644 --- a/behaviors/PixelDecay.xml +++ b/behaviors/PixelDecay.xml @@ -1,12 +1,3 @@ - behaviors.AddPixelEvent diff --git a/behaviors/RandomWalk.py b/behaviors/RandomWalk.py index 8254430..5a32792 100644 --- a/behaviors/RandomWalk.py +++ b/behaviors/RandomWalk.py @@ -1,5 +1,18 @@ from operationscore.Behavior import * import util.ComponentRegistry as compReg +import util.Geo as Geo +import util.Strings as Strings +import random class RandomWalk(Behavior): def processResponse(self, sensors, recursives): + ret = [] + for sensory in sensors: + s = self['StepSize'] + step = [random.randint(-s,s), random.randint(-s,s)] + outdict = dict(sensory) + outdict[Strings.LOCATION] = Geo.addLocations(step, outdict[Strings.LOCATION]) + ret.append(outdict) + return (ret,recursives) + + diff --git a/behaviors/ResponseMover.py b/behaviors/ResponseMover.py new file mode 100644 index 0000000..e1faccb --- /dev/null +++ b/behaviors/ResponseMover.py @@ -0,0 +1,15 @@ +import pdb +from operationscore.Behavior import * +import util.ComponentRegistry as compReg +#ResponseMover is a scaffold for behaviors that spawn 'walkers' which act autonomously on input. +#Add a recursive hook to control the movement. +class ResponseMover(Behavior): + def processResponse(self, sensorInputs, recursiveInputs): + newResponses = sensorInputs + ret = [] + for recurInput in recursiveInputs: + outDict = dict(recurInput) + ret.append(outDict) + ret += newResponses + return (ret, ret) + diff --git a/config/Outdoor.xml b/config/Outdoor.xml index 5ad2c58..e3a74f4 100644 --- a/config/Outdoor.xml +++ b/config/Outdoor.xml @@ -72,6 +72,12 @@ behaviors/RandomColor.xml + + + (0,255,0) + (255,0,0) + + behaviors/PixelDecay.xml @@ -129,6 +135,36 @@ gaussmap + + behaviors.BehaviorChain + + randomwalk + + pygame + + + colorchange + mover + decay + + {'mover':'randmovement'} + True + gaussmap + + + + behaviors.ResponseMover + + mover + + + + behaviors.RandomWalk + + randmovement + 2 + + behaviors/Accelerate.xml diff --git a/ga b/ga old mode 100644 new mode 100755 diff --git a/pixelcore/Screen.py b/pixelcore/Screen.py index 3e8ad5e..77bebb6 100644 --- a/pixelcore/Screen.py +++ b/pixelcore/Screen.py @@ -8,6 +8,7 @@ import util.Strings as Strings import util.TimeOps as timeops import itertools import sys +import pdb from logger import main_log #Class representing a collection of Pixels grouped into PixelStrips. Needs a #PixelMapper, currently set via setMapper by may be migrated into the argDict. @@ -91,5 +92,7 @@ class Screen: PixelEvent.addPixelEventIfMissing(responseInfo) currentTime = timeops.time() for (pixel, weight) in pixelWeightList: + if pixel == None: + pdb.set_trace() pixel.processInput(responseInfo['PixelEvent'].scale(weight), 0, currentTime) #TODO: z-index diff --git a/pixelmappers/SimpleMapper.py b/pixelmappers/SimpleMapper.py index 31a48cc..396eca3 100644 --- a/pixelmappers/SimpleMapper.py +++ b/pixelmappers/SimpleMapper.py @@ -13,7 +13,10 @@ class SimpleMapper(PixelMapper): if pixelDist < bestDist: bestPixel = pixel bestDist = pixelDist - return [(bestPixel,1)] + if bestPixel != None: + return [(bestPixel,1)] + else: + return [] else: #{x}>5,{y} Date: Wed, 12 Jan 2011 14:26:12 -0500 Subject: removing pygame dependencies where they are not needed -RCOH --- LightInstallation.py | 1 - pixelcore/PixelStrip.py | 1 - setup.sh | 0 3 files changed, 2 deletions(-) mode change 100644 => 100755 setup.sh diff --git a/LightInstallation.py b/LightInstallation.py index b54a892..7f98473 100644 --- a/LightInstallation.py +++ b/LightInstallation.py @@ -2,7 +2,6 @@ from xml.etree.ElementTree import ElementTree from pixelcore.Screen import * from pixelcore.PixelStrip import * import pdb, sys, time, thread -from pygame.locals import * import util.TimeOps as clock import util.Config as configGetter import util.ComponentRegistry as compReg diff --git a/pixelcore/PixelStrip.py b/pixelcore/PixelStrip.py index fceff8e..662b8fe 100644 --- a/pixelcore/PixelStrip.py +++ b/pixelcore/PixelStrip.py @@ -2,7 +2,6 @@ from pixelcore.Pixel import * import util.Strings as Strings import util.Geo as Geo from pixelevents.StepEvent import * -import pygame import math import pdb #Python class representing a single Pixel strip (usually 50 Pixels) diff --git a/setup.sh b/setup.sh old mode 100644 new mode 100755 -- cgit v1.2.3 From 0af38d1882291523d0d290a638e88be793f27678 Mon Sep 17 00:00:00 2001 From: rcoh Date: Sun, 16 Jan 2011 21:09:26 -0500 Subject: a couple more behavior additions --- behaviors/RandomWalk.py | 2 +- behaviors/RestrictLocation.py | 12 ++++++++++++ util/ComponentRegistry.py | 3 +++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 behaviors/RestrictLocation.py diff --git a/behaviors/RandomWalk.py b/behaviors/RandomWalk.py index 5a32792..dfe6716 100644 --- a/behaviors/RandomWalk.py +++ b/behaviors/RandomWalk.py @@ -6,8 +6,8 @@ import random class RandomWalk(Behavior): def processResponse(self, sensors, recursives): ret = [] + s = self['StepSize'] for sensory in sensors: - s = self['StepSize'] step = [random.randint(-s,s), random.randint(-s,s)] outdict = dict(sensory) outdict[Strings.LOCATION] = Geo.addLocations(step, outdict[Strings.LOCATION]) diff --git a/behaviors/RestrictLocation.py b/behaviors/RestrictLocation.py new file mode 100644 index 0000000..649500f --- /dev/null +++ b/behaviors/RestrictLocation.py @@ -0,0 +1,12 @@ +from operationscore.Behavior import * +import util.ComponentRegistry as compReg +import util.Geo as Geo +import util.Strings as Strings +import random +class RestrictLocation(Behavior): + def behaviorInit(self): + action = self['Action'] + modifyParamArgs = {'ParamType': 'Sensor', + 'ParamName':self['ParamName'],'ParamOp':self['Action']} + + def processInput( diff --git a/util/ComponentRegistry.py b/util/ComponentRegistry.py index 43c4795..776cd17 100644 --- a/util/ComponentRegistry.py +++ b/util/ComponentRegistry.py @@ -31,6 +31,9 @@ def registerComponent(component, cid=None): globals()['Registry'][cid] = component return cid +def verifyUniqueId(cid): + return not cid in globals()['Registry'] + def removeComponent(cid): globals()['Registry'].pop(cid) -- cgit v1.2.3 From f45b5e262c394cf00ef88f7fca1eab1b4de0fec9 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Thu, 20 Jan 2011 14:57:43 -0500 Subject: Rewrite of Behavior parent class to suck less. Lots of bug fixes. Added 'RestrictLocation' which allows events to fire based on their location. --- behaviors/BehaviorChain.py | 4 ++-- behaviors/ColorChangerBehavior.py | 2 +- behaviors/DebugBehavior.py | 2 +- behaviors/EchoBehavior.py | 2 +- behaviors/ModifyParam.py | 8 +++----- behaviors/RandomWalk.py | 1 + behaviors/RecursiveDecay.py | 3 ++- behaviors/RedShift.xml | 9 +++++++++ behaviors/RestrictLocation.py | 28 ++++++++++++++++++++++++++-- config/Outdoor.xml | 20 +++++++++++++++++--- operationscore/Behavior.py | 29 ++++++++--------------------- operationscore/PixelEvent.py | 1 + pixelcore/PixelEventManager.py | 2 ++ pixelcore/Screen.py | 3 --- pixelmappers/SimpleMapper.py | 1 - setup.sh | 0 16 files changed, 74 insertions(+), 41 deletions(-) create mode 100644 behaviors/RedShift.xml create mode 100644 pixelcore/PixelEventManager.py mode change 100755 => 100644 setup.sh diff --git a/behaviors/BehaviorChain.py b/behaviors/BehaviorChain.py index e15bd23..39f4402 100644 --- a/behaviors/BehaviorChain.py +++ b/behaviors/BehaviorChain.py @@ -29,8 +29,8 @@ class BehaviorChain(Behavior): if hookRecurrence != []: main_log.warn('Hook recurrences are not currently supported. Implement it\ yourself or bug russell') - self.feedback[behaviorId] = recurrence - return response + self.feedback[behaviorId] = recurrence + return (response, []) def appendBehavior(behavior): bid = compReg.registerComponent(behavior) #register behavior (will make diff --git a/behaviors/ColorChangerBehavior.py b/behaviors/ColorChangerBehavior.py index e1827eb..2a8d974 100644 --- a/behaviors/ColorChangerBehavior.py +++ b/behaviors/ColorChangerBehavior.py @@ -11,4 +11,4 @@ class ColorChangerBehavior(Behavior): else: newDict['Color'] = color.randomColor() ret.append(newDict) - return (ret, recursiveInputs) + return (ret, []) diff --git a/behaviors/DebugBehavior.py b/behaviors/DebugBehavior.py index 9bf3ea8..34f4106 100644 --- a/behaviors/DebugBehavior.py +++ b/behaviors/DebugBehavior.py @@ -5,4 +5,4 @@ class DebugBehavior(Behavior): def processResponse(self, sensorInputs, recursiveInputs): if sensorInputs != []: main_log.debug('Sensor Inputs: ' + str(sensorInputs)) - return [] + return ([], []) diff --git a/behaviors/EchoBehavior.py b/behaviors/EchoBehavior.py index be0ed14..589c42b 100644 --- a/behaviors/EchoBehavior.py +++ b/behaviors/EchoBehavior.py @@ -9,4 +9,4 @@ class EchoBehavior(Behavior): outDict[Strings.LOCATION] = sensory[Strings.LOCATION] outDict['Color'] = (255,0,0) ret.append(outDict) - return ret + return (ret, []) diff --git a/behaviors/ModifyParam.py b/behaviors/ModifyParam.py index 3701013..f589e05 100644 --- a/behaviors/ModifyParam.py +++ b/behaviors/ModifyParam.py @@ -5,7 +5,7 @@ class ModifyParam(Behavior): def processResponse(self, sensorInputs, recursiveInputs): paramType = self['ParamType'] paramName = self['ParamName'] - paramOp = self['ParamOp'] + paramOp = str(self['ParamOp']) if paramType == 'Sensor': searchSet = sensorInputs elif paramType == 'Recurse': @@ -13,12 +13,10 @@ class ModifyParam(Behavior): else: raise Exception('Unknown Param Type') for behaviorInput in searchSet: - if paramName in behaviorInput: - try: + if paramName in behaviorInput: #TODO: copy -> modify instead of just + #copying paramOp = paramOp.replace('{val}', 'behaviorInput[paramName]') #convert the {val} marker to something we can execute behaviorInput[paramName] = eval(paramOp) - except: - raise Exception('Bad operation. Use things like \'{val}*5\', \'{val}+5\', exp({val}) etc.') if paramType == 'Sensor': #return accordingly return (searchSet, recursiveInputs) if paramType == 'Recurse': diff --git a/behaviors/RandomWalk.py b/behaviors/RandomWalk.py index dfe6716..fd6c2c8 100644 --- a/behaviors/RandomWalk.py +++ b/behaviors/RandomWalk.py @@ -3,6 +3,7 @@ import util.ComponentRegistry as compReg import util.Geo as Geo import util.Strings as Strings import random +import pdb class RandomWalk(Behavior): def processResponse(self, sensors, recursives): ret = [] diff --git a/behaviors/RecursiveDecay.py b/behaviors/RecursiveDecay.py index ee38bc4..218813d 100644 --- a/behaviors/RecursiveDecay.py +++ b/behaviors/RecursiveDecay.py @@ -1,4 +1,5 @@ from operationscore.Behavior import * +import pdb class RecursiveDecay(Behavior): def processResponse(self, sensorInputs, recursiveInputs): ret = [] @@ -9,4 +10,4 @@ class RecursiveDecay(Behavior): response['ResponsesLeft'] -= 1 if response['ResponsesLeft'] > 0: ret.append(response) - return (ret, recursiveInputs) #no direct ouput + return (ret, []) #no direct ouput diff --git a/behaviors/RedShift.xml b/behaviors/RedShift.xml new file mode 100644 index 0000000..a99e2ba --- /dev/null +++ b/behaviors/RedShift.xml @@ -0,0 +1,9 @@ + + behaviors.RestrictLocation + + (255,0,255) + Color + {x}>100 + + + diff --git a/behaviors/RestrictLocation.py b/behaviors/RestrictLocation.py index 649500f..febc9ed 100644 --- a/behaviors/RestrictLocation.py +++ b/behaviors/RestrictLocation.py @@ -1,12 +1,36 @@ from operationscore.Behavior import * import util.ComponentRegistry as compReg +from behaviors.ModifyParam import * import util.Geo as Geo import util.Strings as Strings import random +import pdb class RestrictLocation(Behavior): def behaviorInit(self): action = self['Action'] modifyParamArgs = {'ParamType': 'Sensor', 'ParamName':self['ParamName'],'ParamOp':self['Action']} - - def processInput( + self.locBounds = self['LocationRestriction'] + self.paramModifier = ModifyParam(modifyParamArgs) + if isinstance(self.locBounds, str): + self.locBounds = self.locBounds.replace('{x}', 'l[0]') + self.locBounds = self.locBounds.replace('{y}', 'l[1]') + self.locEval = eval('lambda l:'+self.locBounds) + elif isinstance(self.locBounds, tuple): + if len(self.locBounds) != 4: + raise Exception('Must be in form (xmin,yin,xmax,ymax)') + else: + self.locEval = lambda l:Geo.pointWithinBoundingBox(l,\ + self.LocBounds) + def processResponse(self, sensorInputs, recursiveInputs): + ret = [] + for data in sensorInputs: + if not self.locEval(data['Location']): + (dataOut, recur) = self.paramModifier.immediateProcessInput([data], []) + #behaviors expect lists ^[] + ret += dataOut + else: + ret.append(data) + return (ret, []) + + diff --git a/config/Outdoor.xml b/config/Outdoor.xml index e3a74f4..e137ec4 100644 --- a/config/Outdoor.xml +++ b/config/Outdoor.xml @@ -70,6 +70,9 @@ False + + behaviors/RedShift.xml + behaviors/RandomColor.xml @@ -114,7 +117,7 @@ behaviors/LoopAndDie.xml - 300 + 30 @@ -123,7 +126,7 @@ runcolordecay pygame - randomLoc + colorchange @@ -147,7 +150,7 @@ mover decay - {'mover':'randmovement'} + {'mover':'redwalk'} True gaussmap @@ -158,6 +161,16 @@ mover + + behaviors.BehaviorChain + + redwalk + + randmovement + redshift + + + behaviors.RandomWalk @@ -188,6 +201,7 @@ echo + redshift square slowdecay diff --git a/operationscore/Behavior.py b/operationscore/Behavior.py index d48c1d5..882a290 100644 --- a/operationscore/Behavior.py +++ b/operationscore/Behavior.py @@ -22,21 +22,19 @@ class Behavior(SmootCoreObject): self.behaviorInit() def behaviorInit(self): pass + def addMapper(fn): + def withmap(fn): + return self.addMapperToResponse(fn()) + return withmap def processResponse(self, sensorInputs, recursiveInputs): - pass + raise Exception('ProcessResponse not defined!') def addInput(self, sensorInput): self.sensorResponseQueue.append(sensorInput) #used for behavior chaining def immediateProcessInput(self, sensorInputs, recursiveInputs=[]): - try: - (output,recursions) = self.processResponse(sensorInputs, \ + (outputs,recursions) = self.processResponse(sensorInputs, \ recursiveInputs) - if type(output) != type([]): - output = [output] - return self.addMapperToResponse((output, recursions)) #TODO: use a decorator for this? - except: #deal with behaviors that don't return a tuple. - responses = self.processResponse(sensorInputs, recursiveInputs) - return (self.processResponse(sensorInputs, recursiveInputs),[]) + return self.addMapperToResponse((outputs,recursions)) def addInputs(self, sensorInputs): if type(sensorInputs) == type([]): [self.addInput(sensorInput) for sensorInput in sensorInputs] @@ -54,19 +52,8 @@ class Behavior(SmootCoreObject): return responses return responses def timeStep(self): #TODO: type checking. clean this up - responses = self.processResponse(self.sensorResponseQueue, \ + (outputs, recursions) = self.processResponse(self.sensorResponseQueue, \ self.recursiveResponseQueue) - if type(responses) == type(tuple()) and len(responses) == 2: - (outputs, recursions) = responses - else: - outputs = responses - recursions = [] self.sensorResponseQueue = [] self.recursiveResponseQueue = recursions - if type(outputs) != type([]): - outputs = [outputs] - try: - return self.addMapperToResponse(outputs) #TODO: WTF is up with this? - except: - pass return self.addMapperToResponse(outputs) diff --git a/operationscore/PixelEvent.py b/operationscore/PixelEvent.py index 6b0812f..80d3b9e 100644 --- a/operationscore/PixelEvent.py +++ b/operationscore/PixelEvent.py @@ -2,6 +2,7 @@ #which should return a color, or None if the response is complete. Consider #requiring a generate event. from operationscore.SmootCoreObject import * +from pixelevents.StepEvent import * import util.ColorOps as color class PixelEvent(SmootCoreObject): def init(self): diff --git a/pixelcore/PixelEventManager.py b/pixelcore/PixelEventManager.py new file mode 100644 index 0000000..779a0ce --- /dev/null +++ b/pixelcore/PixelEventManager.py @@ -0,0 +1,2 @@ +class PixelEventManager(object): + def init(self) diff --git a/pixelcore/Screen.py b/pixelcore/Screen.py index 77bebb6..6666235 100644 --- a/pixelcore/Screen.py +++ b/pixelcore/Screen.py @@ -92,7 +92,4 @@ class Screen: PixelEvent.addPixelEventIfMissing(responseInfo) currentTime = timeops.time() for (pixel, weight) in pixelWeightList: - if pixel == None: - pdb.set_trace() pixel.processInput(responseInfo['PixelEvent'].scale(weight), 0, currentTime) #TODO: z-index - diff --git a/pixelmappers/SimpleMapper.py b/pixelmappers/SimpleMapper.py index 396eca3..19d44c4 100644 --- a/pixelmappers/SimpleMapper.py +++ b/pixelmappers/SimpleMapper.py @@ -35,7 +35,6 @@ class SimpleMapper(PixelMapper): if pixelValid: ret.append((pixel, 1)) except Exception as exp: - pdb.set_trace() raise Exception('Bad event condition') return ret diff --git a/setup.sh b/setup.sh old mode 100755 new mode 100644 -- cgit v1.2.3 From a223608dda0751551c6e8688c0c0e1c9a1d4e69c Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Fri, 21 Jan 2011 00:57:14 -0500 Subject: Added a new config file to run the upstairs lights. Modified PixelMapper to track cache hits. Added 60-strip layouts and renderer configs. --- Profile.py | 2 +- behaviors/Accelerate.xml | 2 +- behaviors/RunningBehavior.py | 1 + config/6thFloor.xml | 208 ++++++++++++++++++++++++++++++++++++++++++ layouts/60StripLayout.xml | 182 ++++++++++++++++++++++++++++++++++++ operationscore/PixelMapper.py | 6 ++ pixelcore/Pixel.py | 8 +- pixelcore/Screen.py | 4 +- pixelevents/DecayEvent.py | 6 +- renderers/60StripSeq.xml | 130 ++++++++++++++++++++++++++ renderers/IndoorRenderer.py | 6 +- renderers/PygameRenderer.py | 2 +- util/Config.py | 14 ++- 13 files changed, 551 insertions(+), 20 deletions(-) create mode 100644 config/6thFloor.xml create mode 100644 layouts/60StripLayout.xml create mode 100644 renderers/60StripSeq.xml diff --git a/Profile.py b/Profile.py index 45ccc35..2f180c9 100644 --- a/Profile.py +++ b/Profile.py @@ -1,4 +1,4 @@ import cProfile from LightInstallation import main -command = """main(['', 'config/Outdoor.xml'])""" +command = """main(['', 'config/6thFloor.xml'])""" cProfile.runctx(command, globals(), locals(), filename="smootlight.profile") diff --git a/behaviors/Accelerate.xml b/behaviors/Accelerate.xml index c78195b..f9de077 100644 --- a/behaviors/Accelerate.xml +++ b/behaviors/Accelerate.xml @@ -3,6 +3,6 @@ Sensor StepSize - {val}*1.01 + {val}*1.1 diff --git a/behaviors/RunningBehavior.py b/behaviors/RunningBehavior.py index 92db69b..5eb33f7 100644 --- a/behaviors/RunningBehavior.py +++ b/behaviors/RunningBehavior.py @@ -15,6 +15,7 @@ class RunningBehavior(Behavior): outDict['StepSize'] = self['StepSize'] outDict['Location']= Geo.addLocations(outDict['Location'], (outDict['StepSize']*outDict['Dir'],0)) + if not Geo.pointWithinBoundingBox(outDict['Location'], \ compReg.getComponent('Screen').getSize()): outDict['Dir'] *= -1 diff --git a/config/6thFloor.xml b/config/6thFloor.xml new file mode 100644 index 0000000..9c5f77e --- /dev/null +++ b/config/6thFloor.xml @@ -0,0 +1,208 @@ + + + + + simplemap + + + + layouts/60StripLayout.xml + + + + pixelmappers.SimpleMapper + + simplemap + 20 + + + + pixelmappers.GaussianMapper + + gaussmap + 30 + 0.1 + 10 + 1 + + + + + + renderers/60StripSeq.xml + + + renderers/Pygame.xml + + + + + inputs.PygameInput + + pygame + 10 + + + + + inputs/MouseFollower.xml + + + inputs.RandomLocs + + randomLoc + + + + + + behaviors.EchoBehavior + + echo + 0 + False + + + + behaviors/RedShift.xml + + + behaviors/RandomColor.xml + + + behaviors/PixelDecay.xml + + + behaviors/PixelDecay.xml + + .01 + + + + behaviors.DebugBehavior + + debug + 0 + + pygame + + + + + behaviors.AllPixelsLeft + + pixelsleft + + + + behaviors.Square + + square + 20 + + + + behaviors/LoopAndDie.xml + + 50 + + + + behaviors.BehaviorChain + + runcolordecay + + pygame + + + colorchange + running + decay + + {'running':'acceleratedie'} + True + gaussmap + + + + behaviors.BehaviorChain + + randomwalk + + pygame + + + colorchange + mover + decay + + {'mover':'redwalk'} + True + gaussmap + + + + behaviors.ResponseMover + + mover + + + + behaviors.BehaviorChain + + redwalk + + randmovement + redshift + + + + + behaviors.RandomWalk + + randmovement + 2 + + + + behaviors/Accelerate.xml + + + behaviors.BehaviorChain + + acceleratedie + + accelerate + recursivedecay + + + + + behaviors.BehaviorChain + + mousechaser + + followmouse + tcp + + + echo + redshift + square + slowdecay + + True + + + + behaviors/RunningBehavior.xml + + + diff --git a/layouts/60StripLayout.xml b/layouts/60StripLayout.xml new file mode 100644 index 0000000..30f51c5 --- /dev/null +++ b/layouts/60StripLayout.xml @@ -0,0 +1,182 @@ + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + + layouts/50PixelStrip.xml + + diff --git a/operationscore/PixelMapper.py b/operationscore/PixelMapper.py index e3f2515..1f94fa5 100644 --- a/operationscore/PixelMapper.py +++ b/operationscore/PixelMapper.py @@ -3,8 +3,14 @@ import pdb class PixelMapper(SmootCoreObject): def init(self): self.mem = {} #Dictionary of all seen events + self.totalCalls = 0 + self.cachehits = 0 def mapEvent(self, eventLocation, screen): + self.totalCalls += 1 + if self.totalCalls % 100 == 0: + print self['Id'], self.cachehits / float(self.totalCalls) if eventLocation in self.mem: + self.cachehits += 1 return self.mem[eventLocation] else: self.mem[eventLocation] = self.mappingFunction(eventLocation, screen) diff --git a/pixelcore/Pixel.py b/pixelcore/Pixel.py index b9fc07f..7260e56 100644 --- a/pixelcore/Pixel.py +++ b/pixelcore/Pixel.py @@ -29,10 +29,10 @@ class Pixel: #arg #Add a pixelEvent to the list of active events - def processInput(self,pixelEvent,zindex, currentTime=None): #consider migrating arg to dict + def processInput(self,pixelEvent,zindex, scale=1,currentTime=None): #consider migrating arg to dict if currentTime == None: currentTime = timeops.time() - self.events[currentTime] = (zindex, pixelEvent) + self.events[currentTime] = (zindex,scale, pixelEvent) def clearAllEvents(self): self.events = {} @@ -49,10 +49,10 @@ class Pixel: resultingColor = (0,0,0) colors = [] for eventTime in self.events: #TODO: right color weighting code - (zindex,event) = self.events[eventTime] + (zindex,scale,event) = self.events[eventTime] eventResult = event.state(currentTime-eventTime) if eventResult != None: - colors.append(eventResult) + colors.append(color.multiplyColor(eventResult,scale)) else: deadEvents.append(eventTime) diff --git a/pixelcore/Screen.py b/pixelcore/Screen.py index 6666235..cfadee8 100644 --- a/pixelcore/Screen.py +++ b/pixelcore/Screen.py @@ -73,6 +73,7 @@ class Screen: maxY = max(y, maxY) self.size = (0,0, maxX, maxY) self.sizeValid = True + print self.size return (0, 0, maxX+100, maxY+100) #TODO: cleaner #private @@ -88,8 +89,7 @@ class Screen: #if type(mapper) != type(PixelMapper): # raise Exception('No default mapper specified.') pixelWeightList = mapper.mapEvent(responseInfo['Location'], self) - PixelEvent.addPixelEventIfMissing(responseInfo) currentTime = timeops.time() for (pixel, weight) in pixelWeightList: - pixel.processInput(responseInfo['PixelEvent'].scale(weight), 0, currentTime) #TODO: z-index + pixel.processInput(responseInfo['PixelEvent'], 0,weight, currentTime) #TODO: z-index diff --git a/pixelevents/DecayEvent.py b/pixelevents/DecayEvent.py index 3767729..f8d5ef0 100644 --- a/pixelevents/DecayEvent.py +++ b/pixelevents/DecayEvent.py @@ -4,12 +4,12 @@ from util.ColorOps import * import util.Geo as Geo class DecayEvent(PixelEvent): def initEvent(self): - self.coefficient = float(abs(self['Coefficient'])) - if self['DecayType'] == 'Exponential': + self.coefficient = float(abs(self.Coefficient)) + if self.DecayType == 'Exponential': self.decayType = 1 else: self.decayType = 2 - self.color = self['Color'] + self.color = self.Color #SUBVERTING DESIGN FOR THE SAKE OF EFFICIENCY -- RUSSELL COHEN (2011-01-03-23:18) def state(self,timeDelay): diff --git a/renderers/60StripSeq.xml b/renderers/60StripSeq.xml new file mode 100644 index 0000000..3f5255f --- /dev/null +++ b/renderers/60StripSeq.xml @@ -0,0 +1,130 @@ + + renderers.IndoorRenderer + + indoorRenderer + + 10.32.0.0 + {'strip0.1':1, 'strip0.2':2} + + + 10.32.0.1 + {'strip1.1':1, 'strip1.2':2} + + + 10.32.0.2 + {'strip2.1':1, 'strip2.2':2} + + + 10.32.0.3 + {'strip3.1':1, 'strip3.2':2} + + + 10.32.0.4 + {'strip4.1':1, 'strip4.2':2} + + + 10.32.0.5 + {'strip5.1':1, 'strip5.2':2} + + + 10.32.0.6 + {'strip6.1':1, 'strip6.2':2} + + + 10.32.0.7 + {'strip7.1':1, 'strip7.2':2} + + + 10.32.0.8 + {'strip8.1':1, 'strip8.2':2} + + + 10.32.0.9 + {'strip9.1':1, 'strip9.2':2} + + + 10.32.0.10 + {'strip10.1':1, 'strip10.2':2} + + + 10.32.0.11 + {'strip11.1':1, 'strip11.2':2} + + + 10.32.0.12 + {'strip12.1':1, 'strip12.2':2} + + + 10.32.0.13 + {'strip13.1':1, 'strip13.2':2} + + + 10.32.0.14 + {'strip14.1':1, 'strip14.2':2} + + + 10.32.0.15 + {'strip15.1':1, 'strip15.2':2} + + + 10.32.0.16 + {'strip16.1':1, 'strip16.2':2} + + + 10.32.0.17 + {'strip17.1':1, 'strip17.2':2} + + + 10.32.0.18 + {'strip18.1':1, 'strip18.2':2} + + + 10.32.0.19 + {'strip19.1':1, 'strip19.2':2} + + + 10.32.0.20 + {'strip20.1':1, 'strip20.2':2} + + + 10.32.0.21 + {'strip21.1':1, 'strip21.2':2} + + + 10.32.0.22 + {'strip22.1':1, 'strip22.2':2} + + + 10.32.0.23 + {'strip23.1':1, 'strip23.2':2} + + + 10.32.0.24 + {'strip24.1':1, 'strip24.2':2} + + + 10.32.0.25 + {'strip25.1':1, 'strip25.2':2} + + + 10.32.0.26 + {'strip26.1':1, 'strip26.2':2} + + + 10.32.0.27 + {'strip27.1':1, 'strip27.2':2} + + + 10.32.0.28 + {'strip28.1':1, 'strip28.2':2} + + + 10.32.0.29 + {'strip29.1':1, 'strip29.2':2} + + + 10.32.0.30 + {'strip30.1':1, 'strip30.2':2} + + + diff --git a/renderers/IndoorRenderer.py b/renderers/IndoorRenderer.py index 5f8546a..76ec172 100644 --- a/renderers/IndoorRenderer.py +++ b/renderers/IndoorRenderer.py @@ -21,7 +21,7 @@ class IndoorRenderer(Renderer): self.stripLocations[stripId] = (ip, \ stripsInPowerSupply[stripId]) def render(self, lightSystem, currentTime=timeops.time()): - try: + #try: for pixelStrip in lightSystem.pixelStrips: stripId = pixelStrip.argDict['Id'] (ip, port) = self.stripLocations[stripId] @@ -30,6 +30,6 @@ class IndoorRenderer(Renderer): self.sockets[ip] = network.getConnectedSocket(ip,sock_port) packet = composer.composePixelStripPacket(pixelStrip, port, currentTime) self.sockets[ip].send(packet, 0x00) - except Exception as inst: - print inst + #except Exception as inst: + # print inst diff --git a/renderers/PygameRenderer.py b/renderers/PygameRenderer.py index 24b2d08..9582a03 100644 --- a/renderers/PygameRenderer.py +++ b/renderers/PygameRenderer.py @@ -6,7 +6,7 @@ import pdb class PygameRenderer(Renderer): def initRenderer(self): pygame.init() - self.screen = pygame.display.set_mode((1300,50)) + self.screen = pygame.display.set_mode((1300,500)) self.background = pygame.Surface(self.screen.get_size()) self.background = self.background.convert() self.background.fill(Color('Black')) diff --git a/util/Config.py b/util/Config.py index 1e20cde..c2d8806 100644 --- a/util/Config.py +++ b/util/Config.py @@ -99,12 +99,19 @@ def fileToDict(fileName): def pullArgsFromItem(parentNode): attribArgs = {} for arg in parentNode.attrib: #automatically pull attributes into the argdict - attribArgs[arg] = parentNode.attrib[arg] + attribArgs[arg] = attemptEval(parentNode.attrib[arg]) argNode = parentNode.find('Args') args = generateArgDict(argNode) for key in attribArgs: args[key] = attribArgs[key] return args + +def attemptEval(val): + try: + val = eval(val) + except (NameError, SyntaxError): + val = str(val) + return val def generateArgDict(parentNode, recurse=False): args = {} for arg in parentNode.getchildren(): @@ -113,10 +120,7 @@ def generateArgDict(parentNode, recurse=False): value = generateArgDict(arg, True) else: #convert into python if possible, otherwise don't - try: - value = eval(arg.text) - except (NameError,SyntaxError): - value = str(arg.text) + value = attemptEval(arg.text) if key in args: #build of lists of like-elements if type(args[key]) != type([]): args[key] = [args[key]] -- cgit v1.2.3 From 7f88db7003a03b50dc500bf7bd711407f1a640de Mon Sep 17 00:00:00 2001 From: rcoh Date: Sat, 22 Jan 2011 13:31:56 -0500 Subject: Bouncing behavior. Working out a weird bug. Threading may be involved? --- LightInstallation.py | 1 + behaviors/RestrictLocation.py | 2 +- behaviors/Square.py | 1 + config/6thFloor.xml | 76 ++++++++++++++++++++++++++++++++++++---- layouts/60StripLayout.xml | 60 +++++++++++++++---------------- operationscore/Input.py | 1 - operationscore/PixelAssembler.py | 2 ++ operationscore/PixelMapper.py | 4 ++- operationscore/Renderer.py | 4 +-- pixelevents/SingleFrameEvent.py | 11 +++--- renderers/60StripSeq.xml | 4 --- setup.sh | 0 12 files changed, 117 insertions(+), 49 deletions(-) mode change 100644 => 100755 setup.sh diff --git a/LightInstallation.py b/LightInstallation.py index 7f98473..62d3f6a 100644 --- a/LightInstallation.py +++ b/LightInstallation.py @@ -134,6 +134,7 @@ class LightInstallation(object): main_log.debug(className + 'initialized with args ' + str(args)) #right except Exception as inst: + pdb.set_trace() main_log.error('Failure while initializing ' + className + ' with ' + str(args)) main_log.error(str(inst)) diff --git a/behaviors/RestrictLocation.py b/behaviors/RestrictLocation.py index febc9ed..f6c26ff 100644 --- a/behaviors/RestrictLocation.py +++ b/behaviors/RestrictLocation.py @@ -25,7 +25,7 @@ class RestrictLocation(Behavior): def processResponse(self, sensorInputs, recursiveInputs): ret = [] for data in sensorInputs: - if not self.locEval(data['Location']): + if self.locEval(data['Location']): (dataOut, recur) = self.paramModifier.immediateProcessInput([data], []) #behaviors expect lists ^[] ret += dataOut diff --git a/behaviors/Square.py b/behaviors/Square.py index a6e9401..aef3622 100644 --- a/behaviors/Square.py +++ b/behaviors/Square.py @@ -5,6 +5,7 @@ class Square(Behavior): xLoc = sensory['Location'][0] yLoc = sensory['Location'][1] width = self['Width'] + #sensory['Location'] = 'True' sensory['Location'] =\ '{x}<'+str(xLoc+width)+',{x}>'+str(xLoc-width)+\ ',{y}<'+str(yLoc+width)+',{y}>'+str(yLoc-width) diff --git a/config/6thFloor.xml b/config/6thFloor.xml index 9c5f77e..4c31892 100644 --- a/config/6thFloor.xml +++ b/config/6thFloor.xml @@ -7,6 +7,7 @@ layouts/60StripLayout.xml + @@ -28,9 +29,9 @@ - + renderers/Pygame.xml @@ -79,6 +80,9 @@ behaviors/PixelDecay.xml + + behaviors/SingleFrame.xml + behaviors/PixelDecay.xml @@ -86,6 +90,60 @@ + behaviors.XYMove + + xymove + 5 + 2 + + + + behaviors.RestrictLocation + + xbounce + {val}*-1 + XStep + {x}<0 or {x}>800 + + + + behaviors.RestrictLocation + + ybounce + {val}*-1 + YStep + {y}<0 or {y}>200 + + + + behaviors.BehaviorChain + + movebounce + + xymove + ybounce + xbounce + + + + + behaviors.BehaviorChain + + xymover + + pygame + + + colorchange + mover + decay + + {'mover':'movebounce'} + True + gaussmap + + + behaviors.DebugBehavior debug @@ -108,6 +166,12 @@ 20 + + behaviors.AllPixels + + allpixels + + behaviors/LoopAndDie.xml @@ -127,7 +191,7 @@ decay {'running':'acceleratedie'} - True + False gaussmap @@ -144,7 +208,7 @@ decay {'mover':'redwalk'} - True + False gaussmap @@ -196,9 +260,9 @@ echo redshift square - slowdecay + decay - True + False diff --git a/layouts/60StripLayout.xml b/layouts/60StripLayout.xml index 30f51c5..45ebcb3 100644 --- a/layouts/60StripLayout.xml +++ b/layouts/60StripLayout.xml @@ -1,179 +1,179 @@ - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml layouts/50PixelStrip.xml - + layouts/50PixelStrip.xml diff --git a/operationscore/Input.py b/operationscore/Input.py index 2ee3c3c..69375d3 100644 --- a/operationscore/Input.py +++ b/operationscore/Input.py @@ -14,7 +14,6 @@ class Input(ThreadedSmootCoreObject): def init(self): self.eventQueue = [] if not 'RefreshInterval' in self.argDict: - print 'RefreshInterval not defined. Defaulting to .5s.' self.argDict['RefreshInterval'] = 500 self.parentScope = self.argDict['parentScope'] self.inputInit() diff --git a/operationscore/PixelAssembler.py b/operationscore/PixelAssembler.py index 6878f8a..84f3b0b 100644 --- a/operationscore/PixelAssembler.py +++ b/operationscore/PixelAssembler.py @@ -23,6 +23,8 @@ class PixelAssembler(SmootCoreObject): between adjacent pixels must be less than \ pixelToPixelSpacing.') locations.append(newLocation) + if self['Reverse']: + locations.reverse() return locations def initLayout(self): pass diff --git a/operationscore/PixelMapper.py b/operationscore/PixelMapper.py index 1f94fa5..7e2b0af 100644 --- a/operationscore/PixelMapper.py +++ b/operationscore/PixelMapper.py @@ -1,4 +1,5 @@ from operationscore.SmootCoreObject import * +from logger import main_log import pdb class PixelMapper(SmootCoreObject): def init(self): @@ -8,7 +9,8 @@ class PixelMapper(SmootCoreObject): def mapEvent(self, eventLocation, screen): self.totalCalls += 1 if self.totalCalls % 100 == 0: - print self['Id'], self.cachehits / float(self.totalCalls) + main_log.info('Cache percentage for :', self['Id'], self.cachehits /\ + float(self.totalCalls)) if eventLocation in self.mem: self.cachehits += 1 return self.mem[eventLocation] diff --git a/operationscore/Renderer.py b/operationscore/Renderer.py index ed88a8c..b422304 100644 --- a/operationscore/Renderer.py +++ b/operationscore/Renderer.py @@ -3,8 +3,8 @@ #Inheriting classes may define initRenderer which is called after the dictionary #is pulled from config. #TODO: multithreaded-rendering -from operationscore.ThreadedSmootCoreObject import * -class Renderer(ThreadedSmootCoreObject): +from operationscore.SmootCoreObject import * +class Renderer(SmootCoreObject): def init(self): self.initRenderer() def render(lightSystem): diff --git a/pixelevents/SingleFrameEvent.py b/pixelevents/SingleFrameEvent.py index 1c6239f..97a2681 100644 --- a/pixelevents/SingleFrameEvent.py +++ b/pixelevents/SingleFrameEvent.py @@ -1,8 +1,11 @@ from operationscore.PixelEvent import * class SingleFrameEvent(PixelEvent): def initEvent(self): - self.rendered = False - def state(self): - if !self.rendered: - return self['Color'] + self.timeState = -1 + def state(self, timeDelay): + print 'singlehit' + + if self.timeState == (-1 or timeDelay): + self.timeState = timeDelay + return self.Color return None diff --git a/renderers/60StripSeq.xml b/renderers/60StripSeq.xml index 3f5255f..49e2c25 100644 --- a/renderers/60StripSeq.xml +++ b/renderers/60StripSeq.xml @@ -2,10 +2,6 @@ renderers.IndoorRenderer indoorRenderer - - 10.32.0.0 - {'strip0.1':1, 'strip0.2':2} - 10.32.0.1 {'strip1.1':1, 'strip1.2':2} diff --git a/setup.sh b/setup.sh old mode 100644 new mode 100755 -- cgit v1.2.3 From 2019fb2895237aa9d86450daaf6d90831189fc13 Mon Sep 17 00:00:00 2001 From: rcoh Date: Mon, 24 Jan 2011 22:44:16 -0500 Subject: Some new stuff. Fixed a bug where screen responses weren't being synchronized. Now they are. Added XYMove to do a bouncy behavior. --- LightInstallation.py | 8 ++++---- behaviors/AllPixels.py | 6 ++++++ behaviors/SingleFrame.xml | 7 +++++++ behaviors/XYMove.py | 17 +++++++++++++++++ config/6thFloor.xml | 8 ++++---- operationscore/Behavior.py | 2 ++ pixelcore/Pixel.py | 6 +++++- pixelcore/Screen.py | 17 ++++++++++++----- pixelevents/SingleFrameEvent.py | 5 ++--- pixelmappers/GaussianMapper.py | 2 ++ 10 files changed, 61 insertions(+), 17 deletions(-) create mode 100644 behaviors/AllPixels.py create mode 100644 behaviors/SingleFrame.xml create mode 100644 behaviors/XYMove.py diff --git a/LightInstallation.py b/LightInstallation.py index 62d3f6a..b582bd2 100644 --- a/LightInstallation.py +++ b/LightInstallation.py @@ -146,19 +146,19 @@ class LightInstallation(object): def mainLoop(self): lastLoopTime = clock.time() refreshInterval = 30 - runCount = 2000 - while runCount > 0 and not self.dieNow: - runCount -= 1 + while not self.dieNow: loopStart = clock.time() responses = self.evaluateBehaviors() #inputs are all queued when they #happen, so we only need to run the behaviors self.timer.start() [self.screen.respond(response) for response in responses if response != []] - self.screen.timeStep() + self.screen.timeStep(loopStart) [r.render(self.screen, loopStart) for r in self.renderers] loopElapsed = clock.time()-loopStart sleepTime = max(0,refreshInterval-loopElapsed) + main_log.debug('Loop complete in ' + str(loopElapsed) + 'ms. Sleeping for ' +\ + str(sleepTime)) self.timer.stop() #print self.timer.elapsed() if sleepTime > 0: diff --git a/behaviors/AllPixels.py b/behaviors/AllPixels.py new file mode 100644 index 0000000..e155e55 --- /dev/null +++ b/behaviors/AllPixels.py @@ -0,0 +1,6 @@ +from operationscore.Behavior import * +class AllPixels(Behavior): + def processResponse(self, sensorInputs, recursiveInputs): + for sensory in sensorInputs:#TODO: consider replicating the dict + sensory['Location'] = 'True' + return (sensorInputs, recursiveInputs) diff --git a/behaviors/SingleFrame.xml b/behaviors/SingleFrame.xml new file mode 100644 index 0000000..49b5dcf --- /dev/null +++ b/behaviors/SingleFrame.xml @@ -0,0 +1,7 @@ + + behaviors.AddPixelEvent + + pixelevents.SingleFrameEvent + 0 + + diff --git a/behaviors/XYMove.py b/behaviors/XYMove.py new file mode 100644 index 0000000..8acbba8 --- /dev/null +++ b/behaviors/XYMove.py @@ -0,0 +1,17 @@ +from operationscore.Behavior import * +import util.Geo as Geo +class XYMove(Behavior): + def processResponse(self, sensor, recurs): + ret = [] + for loc in sensor: + oploc = dict(loc) + self.insertStepIfMissing(oploc) + oploc['Location'] = Geo.addLocations((oploc['XStep'], oploc['YStep']), oploc['Location']) + ret.append(oploc) + return (ret, []) + def insertStepIfMissing(self, data): + if not 'XStep' in data: + data['XStep'] = self['XStep'] + if not 'YStep' in data: + data['YStep'] = self['YStep'] + diff --git a/config/6thFloor.xml b/config/6thFloor.xml index 4c31892..3f734d9 100644 --- a/config/6thFloor.xml +++ b/config/6thFloor.xml @@ -29,9 +29,9 @@ - + renderers/Pygame.xml @@ -260,9 +260,9 @@ echo redshift square - decay + singleframe - False + True diff --git a/operationscore/Behavior.py b/operationscore/Behavior.py index 882a290..b3f7342 100644 --- a/operationscore/Behavior.py +++ b/operationscore/Behavior.py @@ -9,6 +9,7 @@ import pdb from operationscore.SmootCoreObject import * +from logger import main_log #timeStep is called on every iteration of the LightInstallation #addInput is called on each individual input received, and the inputs queue class Behavior(SmootCoreObject): @@ -56,4 +57,5 @@ class Behavior(SmootCoreObject): self.recursiveResponseQueue) self.sensorResponseQueue = [] self.recursiveResponseQueue = recursions + main_log.debug(self['Id'] + ' Ouputs ' + str(outputs)) return self.addMapperToResponse(outputs) diff --git a/pixelcore/Pixel.py b/pixelcore/Pixel.py index 7260e56..1fbea2c 100644 --- a/pixelcore/Pixel.py +++ b/pixelcore/Pixel.py @@ -52,7 +52,11 @@ class Pixel: (zindex,scale,event) = self.events[eventTime] eventResult = event.state(currentTime-eventTime) if eventResult != None: - colors.append(color.multiplyColor(eventResult,scale)) + scaledEvent = color.multiplyColor(eventResult,scale) + if (scaledEvent[0] + scaledEvent[1] + scaledEvent[2]) < 5: + deadEvents.append(eventTime) + else: + colors.append(scaledEvent) else: deadEvents.append(eventTime) diff --git a/pixelcore/Screen.py b/pixelcore/Screen.py index cfadee8..a6fc8c4 100644 --- a/pixelcore/Screen.py +++ b/pixelcore/Screen.py @@ -49,11 +49,15 @@ class Screen: #increment time -- This processes all queued responses. Responses generated #during this period are added to the queue that will be processed on the next #time step. - def timeStep(self): + #SUBVERTING DESIGN FOR EFFICIENCY 1/24/11, RCOH -- It would be cleaner to store the time on the responses + #themselves, however, it is faster to just pass it in. + def timeStep(self, currentTime=None): + if currentTime == None: + currentTime = timeops.time() tempQueue = list(self.responseQueue) self.responseQueue = [] for response in tempQueue: - self.processResponse(response) + self.processResponse(response, currentTime) #public def respond(self, responseInfo): @@ -73,13 +77,15 @@ class Screen: maxY = max(y, maxY) self.size = (0,0, maxX, maxY) self.sizeValid = True - print self.size return (0, 0, maxX+100, maxY+100) #TODO: cleaner #private - def processResponse(self, responseInfo): #we need to make a new dict for + def processResponse(self, responseInfo, currentTime=None): #we need to make a new dict for #each to prevent interference #[strip.respond(dict(responseInfo)) for strip in self.pixelStrips] + if currentTime == None: + currentTime = timeops.time() + print 'cachetime fail' if type(responseInfo) != type(dict()): pass if 'Mapper' in responseInfo: @@ -89,7 +95,8 @@ class Screen: #if type(mapper) != type(PixelMapper): # raise Exception('No default mapper specified.') pixelWeightList = mapper.mapEvent(responseInfo['Location'], self) + main_log.debug('Screen processing response. ' + str(len(pixelWeightList)) + ' events\ +generated') PixelEvent.addPixelEventIfMissing(responseInfo) - currentTime = timeops.time() for (pixel, weight) in pixelWeightList: pixel.processInput(responseInfo['PixelEvent'], 0,weight, currentTime) #TODO: z-index diff --git a/pixelevents/SingleFrameEvent.py b/pixelevents/SingleFrameEvent.py index 97a2681..767f403 100644 --- a/pixelevents/SingleFrameEvent.py +++ b/pixelevents/SingleFrameEvent.py @@ -3,9 +3,8 @@ class SingleFrameEvent(PixelEvent): def initEvent(self): self.timeState = -1 def state(self, timeDelay): - print 'singlehit' - - if self.timeState == (-1 or timeDelay): + if self.timeState == -1: self.timeState = timeDelay + if self.timeState == timeDelay: return self.Color return None diff --git a/pixelmappers/GaussianMapper.py b/pixelmappers/GaussianMapper.py index 686ebcd..c82f243 100644 --- a/pixelmappers/GaussianMapper.py +++ b/pixelmappers/GaussianMapper.py @@ -4,6 +4,8 @@ class GaussianMapper(PixelMapper): def mappingFunction(self, eventLocation, screen): returnPixels = [] #TODO: consider preallocation and trimming [x,y] = eventLocation + potentialPixels = screen.pixelsInRange(x-self.CutoffDist, \ + x+self.CutoffDist) for (x,pixel) in screen.pixelsInRange(x-self.CutoffDist, \ x+self.CutoffDist): pixelDist = Geo.dist(pixel.location, eventLocation) -- cgit v1.2.3 From 5fb3ea060025241105dc8e9a174513c112f9a133 Mon Sep 17 00:00:00 2001 From: rcoh Date: Thu, 27 Jan 2011 16:50:59 -0500 Subject: A metric $#%$-ton of changes. Added doc-strings to EVERYTHING. Phew. Fixed a massive bug that increases performance in by up to a factor of 60. A bunch of new behaviors for the class. --- LightInstallation.py | 44 ++++++---------- behaviors/AddPixelEvent.py | 5 ++ behaviors/AllPixels.py | 11 ++-- behaviors/AllPixelsLeft.py | 1 + behaviors/BehaviorChain.py | 23 +++++++-- behaviors/ColorChangerBehavior.py | 18 ++++++- behaviors/DebugBehavior.py | 3 ++ behaviors/DecayBehavior.py | 1 + behaviors/DimColor.xml | 2 +- behaviors/DoorBehavior.py | 4 -- behaviors/EchoBehavior.py | 2 + behaviors/Expand.py | 4 ++ behaviors/MITDoors.py | 20 ++++++++ behaviors/ModifyParam.py | 9 ++++ behaviors/MoveBehavior.py | 3 ++ behaviors/RandomWalk.py | 3 ++ behaviors/RecursiveDecay.py | 5 ++ behaviors/ResponseMover.py | 6 ++- behaviors/RestrictLocation.py | 10 ++++ behaviors/RiseFall.py | 40 +++++++++++++++ behaviors/RiseFall.xml | 7 +++ behaviors/RunningBehavior.py | 5 ++ behaviors/Square.py | 25 +++++---- behaviors/TimedDie.py | 15 ++++++ behaviors/Timeout.py | 16 ++++++ behaviors/XYMove.py | 7 +++ config/6thFloor.xml | 85 ++++++++++++++++--------------- inputs/PygameInput.py | 35 ++++++++----- inputs/RandomLocs.py | 3 ++ inputs/TCPInput.py | 9 ++-- inputs/UDPInput.py | 4 ++ layouts/LineLayout.py | 2 +- layouts/ZigzagLayout.py | 9 ++++ operationscore/Behavior.py | 18 ++++--- operationscore/Input.py | 14 +++-- operationscore/PixelEvent.py | 7 ++- operationscore/PixelMapper.py | 8 ++- operationscore/Renderer.py | 8 +-- operationscore/SmootCoreObject.py | 9 ++-- operationscore/ThreadedSmootCoreObject.py | 2 + pixelcore/Pixel.py | 32 +++++------- pixelcore/PixelEventManager.py | 2 - pixelcore/PixelStrip.py | 29 +++-------- pixelcore/Screen.py | 13 +++-- pixelevents/DecayEvent.py | 4 ++ pixelevents/SingleFrameEvent.py | 3 ++ pixelmappers/GaussianMapper.py | 10 +++- pixelmappers/SimpleMapper.py | 5 +- renderers/IndoorRenderer.py | 6 +-- renderers/PygameRenderer.py | 7 ++- tests/TestComponentRegistry.py | 3 ++ tests/TestConfigLoaders.py | 3 ++ util/ColorOps.py | 5 ++ util/Config.py | 26 ++++++---- util/Geo.py | 9 +++- util/NetworkOps.py | 1 - util/PacketComposition.py | 9 +++- util/Search.py | 6 +-- util/TimeOps.py | 1 + 59 files changed, 459 insertions(+), 217 deletions(-) delete mode 100644 behaviors/DoorBehavior.py create mode 100644 behaviors/MITDoors.py create mode 100644 behaviors/RiseFall.py create mode 100644 behaviors/RiseFall.xml create mode 100644 behaviors/TimedDie.py create mode 100644 behaviors/Timeout.py delete mode 100644 pixelcore/PixelEventManager.py diff --git a/LightInstallation.py b/LightInstallation.py index b582bd2..ad1e38d 100644 --- a/LightInstallation.py +++ b/LightInstallation.py @@ -89,8 +89,8 @@ class LightInstallation(object): self.inputs = inputs for inputClass in inputs: inputClass.start() - self.inputBehaviorRegistry[inputClass['Id']] = [] - #empty list is list of bound behaviors + self.inputBehaviorRegistry[inputClass['Id']] = [] #Bound behaviors will be added to this + #list def initializeRenderers(self, rendererConfig): self.renderers = self.initializeComponent(rendererConfig) @@ -98,13 +98,9 @@ class LightInstallation(object): def registerComponents(self, components): for component in components: cid = compReg.registerComponent(component) - main_log.debug(cid + ' registered') - cid = component['Id'] - if cid == None: #TODO: determine if componenent is critical, and if so, die - main_log.error('Components must be registered with Ids. Component not registered') - else: - compReg.registerComponent(component) - main_log.debug(cid + ' registered') + main_log.info(cid + ' registered') + compReg.registerComponent(component) + main_log.info(cid + ' registered') def initializeComponent(self, config): components = [] @@ -114,7 +110,6 @@ class LightInstallation(object): [module,className] = configItem.find('Class').text.split('.') except: main_log.error('Module must have Class element') - main_log.warn('Module without class element. Module not initialized') continue try: exec('from ' + module+'.'+className + ' import *') @@ -125,16 +120,13 @@ class LightInstallation(object): main_log.error(str(inst)) continue args = configGetter.pullArgsFromItem(configItem) - args['parentScope'] = self #TODO: we shouldn't give away scope - #like this, find another way. + args['parentScope'] = self try: new_component = eval(className+'(args)') new_component.addDieListener(self) - components.append(new_component) #TODO: doesn't error - main_log.debug(className + 'initialized with args ' + str(args)) - #right + components.append(new_component) + main_log.info(className + 'initialized with args ' + str(args)) except Exception as inst: - pdb.set_trace() main_log.error('Failure while initializing ' + className + ' with ' + str(args)) main_log.error(str(inst)) @@ -146,10 +138,9 @@ class LightInstallation(object): def mainLoop(self): lastLoopTime = clock.time() refreshInterval = 30 - while not self.dieNow: + while not self.dieNow: #dieNow is set if one of its constituents sends a die request. loopStart = clock.time() - responses = self.evaluateBehaviors() #inputs are all queued when they - #happen, so we only need to run the behaviors + responses = self.evaluateBehaviors() self.timer.start() [self.screen.respond(response) for response in responses if response != []] @@ -160,19 +151,17 @@ class LightInstallation(object): main_log.debug('Loop complete in ' + str(loopElapsed) + 'ms. Sleeping for ' +\ str(sleepTime)) self.timer.stop() - #print self.timer.elapsed() if sleepTime > 0: time.sleep(sleepTime/1000) - #evaluates all the behaviors (including inter-dependencies) and returns a - #list of responses to go to the screen. def evaluateBehaviors(self): + """Evaluates all the behaviors (including inter-dependencies) and returns a list of responses to + go to the screen""" 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. + if behavior['RenderToScreen'] == True: responses['Screen'] += responses[behavior['Id']] return responses['Screen'] @@ -181,9 +170,9 @@ class LightInstallation(object): for behavior in self.behaviors: self.addBehavior(behavior) - #Does work needed to add a behavior: currently -- maps behavior inputs into - #the input behavior registry. def addBehavior(self, behavior): + """Does work needed to add a behavior: currently -- maps behavior inputs into the input behavior + registry""" for inputId in behavior.argDict['Inputs']: if inputId in self.inputBehaviorRegistry: #it could be a behavior self.inputBehaviorRegistry[inputId].append(behavior['Id']) @@ -191,12 +180,11 @@ class LightInstallation(object): def processResponse(self,inputDict, responseDict): inputId = inputDict['Id'] boundBehaviorIds = self.inputBehaviorRegistry[inputId] - #TODO: fix this, it crashes because inputs get run before beahviors exist try: [compReg.getComponent(b).addInput(responseDict) for b in boundBehaviorIds] except: pass - #print 'Behaviors not initialized yet. WAIT!' + #Behavior run before loading. Not a big deal. def handleDie(self, caller): self.dieNow = True diff --git a/behaviors/AddPixelEvent.py b/behaviors/AddPixelEvent.py index 7f134e1..821f432 100644 --- a/behaviors/AddPixelEvent.py +++ b/behaviors/AddPixelEvent.py @@ -2,6 +2,9 @@ from operationscore.Behavior import * import util.Strings as Strings from logger import main_log class AddPixelEvent(Behavior): + """AddPixelEvent is a behavior to append an arbitrary PixelEvent to a behavior response. The + classname of the PixelEvent should be specified in the Class field of Args. All arguments normally + passed to the PixelEvent should also be specified in Args.""" def behaviorInit(self): [module, className] = self['Class'].split('.') try: @@ -18,6 +21,8 @@ class AddPixelEvent(Behavior): ret = [] for sensory in sensors: outDict = {} + if not 'Location' in sensory: + pdb.set_trace() outDict[Strings.LOCATION] = sensory[Strings.LOCATION] settingsDict = dict(self.argDict) settingsDict['Color'] = sensory['Color'] diff --git a/behaviors/AllPixels.py b/behaviors/AllPixels.py index e155e55..7f66ad6 100644 --- a/behaviors/AllPixels.py +++ b/behaviors/AllPixels.py @@ -1,6 +1,9 @@ from operationscore.Behavior import * class AllPixels(Behavior): - def processResponse(self, sensorInputs, recursiveInputs): - for sensory in sensorInputs:#TODO: consider replicating the dict - sensory['Location'] = 'True' - return (sensorInputs, recursiveInputs) + """Turns on all Pixels in the installation. Must use SimpleMapper, or other Mapper supporting + conditional pixel locations.""" + + def processResponse(self, sensorInputs, recursiveInputs): + for sensory in sensorInputs:#TODO: consider replicating the dict + sensory['Location'] = 'True' + return (sensorInputs, recursiveInputs) diff --git a/behaviors/AllPixelsLeft.py b/behaviors/AllPixelsLeft.py index b48bfc1..b223156 100644 --- a/behaviors/AllPixelsLeft.py +++ b/behaviors/AllPixelsLeft.py @@ -2,6 +2,7 @@ from operationscore.Behavior import * import util.ComponentRegistry as compReg import pdb class AllPixelsLeft(Behavior): + """Behavior which returns all points left of its input. No Args.""" def processResponse(self, sensorInputs, recursiveInputs): for sensory in sensorInputs: xLoc = sensory['Location'][0] diff --git a/behaviors/BehaviorChain.py b/behaviors/BehaviorChain.py index 39f4402..631ad98 100644 --- a/behaviors/BehaviorChain.py +++ b/behaviors/BehaviorChain.py @@ -3,6 +3,24 @@ import util.ComponentRegistry as compReg from logger import main_log import pdb class BehaviorChain(Behavior): + """BehaviorChain is a class which chains together multiple behavior. BehaviorChain is in itself a + behavior, and behaves and can be used accordingly. BehaviorChain also supports recursive hooks to + be set on its constituent behaviors. ChainedBehaviors should be specified in as follows: + + + behavior1Id + behavior2Id + + + Behaviors may also be appended programmatically via the appendBehavior method. + + Recursive hooks should be specified with Python dict syntax as follows: + + {'behavior1Id':'hookid'} + + Behavior Chain manages all recurrences that its constituents propogate. At this point, it does not + support recurrences in its hooks.""" + def behaviorInit(self): self.feedback = {} #dictionary to allow feedback of recursives self.hooks = self['RecursiveHooks'] @@ -27,11 +45,10 @@ class BehaviorChain(Behavior): hookBehavior.immediateProcessInput(recurrence, \ []) if hookRecurrence != []: - main_log.warn('Hook recurrences are not currently supported. Implement it\ -yourself or bug russell') + main_log.warn('Hook recurrences are not currently supported.') self.feedback[behaviorId] = recurrence return (response, []) - + def appendBehavior(behavior): bid = compReg.registerComponent(behavior) #register behavior (will make #a new id if there isn't one) diff --git a/behaviors/ColorChangerBehavior.py b/behaviors/ColorChangerBehavior.py index 2a8d974..e2f8bd3 100644 --- a/behaviors/ColorChangerBehavior.py +++ b/behaviors/ColorChangerBehavior.py @@ -2,12 +2,26 @@ from operationscore.Behavior import * import util.ColorOps as color import pdb class ColorChangerBehavior(Behavior): + """ColorChangerBehavior is a behavior for adding colors to responses. If given no arguments, it + will generate a random color. If it is given a list of colors [as below] it will pick randomly + from them. + + + (255,0,0) + (30,79,200) + + + ColorList also supports specification of a single color.""" + def processResponse(self, sensorInputs, recursiveInputs): ret = [] for sensory in sensorInputs: - newDict = dict(sensory) #don't run into shallow copy issues + newDict = dict(sensory) if self['ColorList'] != None: - newDict['Color'] = color.chooseRandomColor(self['ColorList']) #TODO: this doesn't work. + if isinstance(self['ColorList'], list): + newDict['Color'] = color.chooseRandomColor(self['ColorList']) #Pick randomly + else: + newDict['Color'] = self['ColorList'] #Unless there is only one else: newDict['Color'] = color.randomColor() ret.append(newDict) diff --git a/behaviors/DebugBehavior.py b/behaviors/DebugBehavior.py index 17383db..8f81954 100644 --- a/behaviors/DebugBehavior.py +++ b/behaviors/DebugBehavior.py @@ -2,6 +2,9 @@ from operationscore.Behavior import * from logger import main_log import pdb class DebugBehavior(Behavior): + """DebugBehavior simply writes all of its inputs to the logs, currently at the ERROR level for + easy visibility. Will be changed to DEBUG or INFO in the future""" + def processResponse(self, sensorInputs, recursiveInputs): if sensorInputs != []: main_log.error('Sensor Inputs: ' + str(sensorInputs)) diff --git a/behaviors/DecayBehavior.py b/behaviors/DecayBehavior.py index c1f6f92..f19ffc8 100644 --- a/behaviors/DecayBehavior.py +++ b/behaviors/DecayBehavior.py @@ -3,6 +3,7 @@ from pixelevents.DecayEvent import * import util.Strings as Strings import pdb class DecayBehavior(Behavior): + """DecayBehavior is obsolete. Use AddPixelEvent instead""" def processResponse(self, sensorInputs, recursiveInputs): ret = [] for sensory in sensorInputs: diff --git a/behaviors/DimColor.xml b/behaviors/DimColor.xml index b01908e..58b0673 100644 --- a/behaviors/DimColor.xml +++ b/behaviors/DimColor.xml @@ -3,6 +3,6 @@ Sensor Color - [chan*.99 for chan in {val}] + [chan*.95 for chan in {val}] diff --git a/behaviors/DoorBehavior.py b/behaviors/DoorBehavior.py deleted file mode 100644 index 5058f69..0000000 --- a/behaviors/DoorBehavior.py +++ /dev/null @@ -1,4 +0,0 @@ -from operationscore.behavior import * -class DoorBehavior.py(Behavior): - def behaviorInit(self): - pyt diff --git a/behaviors/EchoBehavior.py b/behaviors/EchoBehavior.py index 589c42b..6ef4fcb 100644 --- a/behaviors/EchoBehavior.py +++ b/behaviors/EchoBehavior.py @@ -2,6 +2,8 @@ from operationscore.Behavior import * import util.Strings as Strings import pdb class EchoBehavior(Behavior): + """EchoBehavior generates a RED response at all locations specified in sensorInputs. Useful for + debugging""" def processResponse(self, sensorInputs, recursiveInputs): ret = [] for sensory in sensorInputs: diff --git a/behaviors/Expand.py b/behaviors/Expand.py index 3415966..323e71f 100644 --- a/behaviors/Expand.py +++ b/behaviors/Expand.py @@ -1,5 +1,9 @@ from operationscore.Behavior import * class Expand(Behavior): + """Expand is a behavior that generates a response that grows horizontally starting a location + specifed in input. Required Args: + 123 which is the expandrate in units/response""" + def processResponse(self, sensorInputs, recurs): ret = [] for data in sensorInputs: diff --git a/behaviors/MITDoors.py b/behaviors/MITDoors.py new file mode 100644 index 0000000..d602a55 --- /dev/null +++ b/behaviors/MITDoors.py @@ -0,0 +1,20 @@ +from operationscore.Behavior import * +class MITDoors(Behavior): + """MITDoors is a case-specific behavior to map keypresses to specific locations. Written for + Kuan 1/26/11 by RCOH""" + + def behaviorInit(self): + self.keymapping = {'q':[2,19], 'w':[22,36], 'e':[37,49], 'r':[52,69], 't':[76,91], 'y':[94,105], + 'u':[106,117], 'i':[123,154], 'o':[158,161], 'p':[164,167], '[':[172,184]} + def processResponse(self, sensorInputs, recursiveInputs): + ret = [] + for data in sensorInputs: + key = chr(data['Key']) + if key in self.keymapping: + bounds = self.keymapping[key] + data = dict(data) + data['Left'], data['Right'] = bounds + data['Bottom'] = self['Bottom'] + data['Location'] = (sum(bounds) / 2., self['Bottom']) + ret.append(data) + return (ret, []) diff --git a/behaviors/ModifyParam.py b/behaviors/ModifyParam.py index f589e05..4f45be0 100644 --- a/behaviors/ModifyParam.py +++ b/behaviors/ModifyParam.py @@ -2,8 +2,17 @@ from operationscore.Behavior import * import pdb #Class to perform a given operation on some element of an argDict. Designed to be used a recursive hook, but can serve sensor-based functions as well. Specify ParamType (Sensor or Recurse), ParamName, and ParamOp, (a valid python statement with the old value represented as {val}) class ModifyParam(Behavior): + """ModifyParam is a powerful class to perform an action on a specified key in the Argument + Dictionary of a response. Specify: + -- Sensor or Recurse + -- The name of the parameter you wish to modify + -- The modification you wish to do. Use {val} to specify the current value of the + parameter in question. Special hooks for {x} and {y} exist in some versions""" + def processResponse(self, sensorInputs, recursiveInputs): paramType = self['ParamType'] + if paramType == None: + paramType = 'Sensor' paramName = self['ParamName'] paramOp = str(self['ParamOp']) if paramType == 'Sensor': diff --git a/behaviors/MoveBehavior.py b/behaviors/MoveBehavior.py index 590f0e4..d2f60a0 100644 --- a/behaviors/MoveBehavior.py +++ b/behaviors/MoveBehavior.py @@ -4,6 +4,9 @@ import util.Geo as Geo import util.Strings as Strings class MoveBehavior(Behavior): + """Moves current location by the x and y components of sensorInput. Uses recurrences to track + current input. @Author: Euguene""" + def processResponse(self, sensorInputs, recursiveInputs): if recursiveInputs: currRecLocs = recursiveInputs diff --git a/behaviors/RandomWalk.py b/behaviors/RandomWalk.py index fd6c2c8..c9049af 100644 --- a/behaviors/RandomWalk.py +++ b/behaviors/RandomWalk.py @@ -5,6 +5,9 @@ import util.Strings as Strings import random import pdb class RandomWalk(Behavior): + """Behavior to move the curent location by a random distance specified by + -- StepSize in units/response""" + def processResponse(self, sensors, recursives): ret = [] s = self['StepSize'] diff --git a/behaviors/RecursiveDecay.py b/behaviors/RecursiveDecay.py index 218813d..0ae21ea 100644 --- a/behaviors/RecursiveDecay.py +++ b/behaviors/RecursiveDecay.py @@ -1,6 +1,11 @@ from operationscore.Behavior import * import pdb class RecursiveDecay(Behavior): + """RecursiveDecay is an event to allow recursive hooks to stop recursing after a certain number + of iterations specified in + -- Int, number of total responses. + Designed to be used as part of a recursive hook. + """ def processResponse(self, sensorInputs, recursiveInputs): ret = [] for response in sensorInputs: diff --git a/behaviors/ResponseMover.py b/behaviors/ResponseMover.py index e1faccb..59e353a 100644 --- a/behaviors/ResponseMover.py +++ b/behaviors/ResponseMover.py @@ -1,9 +1,11 @@ import pdb from operationscore.Behavior import * import util.ComponentRegistry as compReg -#ResponseMover is a scaffold for behaviors that spawn 'walkers' which act autonomously on input. -#Add a recursive hook to control the movement. class ResponseMover(Behavior): + """ResponseMover is a scaffold for behaviors that spawn 'walkers' which act autonomously on input. + To control the movment, use the behavior as part of a BehaviorChain and add a recursive hook which + modulates the location.""" + def processResponse(self, sensorInputs, recursiveInputs): newResponses = sensorInputs ret = [] diff --git a/behaviors/RestrictLocation.py b/behaviors/RestrictLocation.py index f6c26ff..5e12440 100644 --- a/behaviors/RestrictLocation.py +++ b/behaviors/RestrictLocation.py @@ -6,6 +6,16 @@ import util.Strings as Strings import random import pdb class RestrictLocation(Behavior): + """RestrictLocation is a Behavior which does an action -- A ModifyParam, actually, when a certain + location based condition is met. It takes arguments as follows: + + -- Operation to perform, using ModifyParam syntax. Use {val} to reference the variable + specified by ParamName. + -- the name of the parameter to modify. + -- either a tuple of (xmin,ymin,xmax,ymax) or a python-correct conditional. Use {x} and + {y} to reference x and y. Use < and > to get < and > in XML. EG: + {x}<0 or {x}>800""" + def behaviorInit(self): action = self['Action'] modifyParamArgs = {'ParamType': 'Sensor', diff --git a/behaviors/RiseFall.py b/behaviors/RiseFall.py new file mode 100644 index 0000000..109cd10 --- /dev/null +++ b/behaviors/RiseFall.py @@ -0,0 +1,40 @@ +from operationscore.Behavior import * +import math +import util.TimeOps as timeOps +#Required Args: +#Period (ms), MaxHeight, Width +class RiseFall(Behavior): + """RiseFall is a behavior that creates a rising and falling column of light. Specify: + -- the maximum height that it rises to. + -- the width of the column OR and + -- the period of oscillation in ms + + Designed to be used as part of a recursive hook. + """ + + def processResponse(self, sensorInputs, recurInputs): + ret = [] + for data in sensorInputs: + #first time with behavior: + data = dict(data) + if not 'StartTime' in data: + data['StartTime'] = timeOps.time() + data['Period'] = self['Period'] + data['MaxHeight'] = self['MaxHeight'] #Consider just using += + if not 'Bottom' in data: + data['Bottom'] = data['Location'][1] + if 'Width' in self: #TODO: improve + data['Width'] = self['Width'] + data['Left'] = data['Location'][0]-data['Width']/2. + data['Right'] = data['Location'][0]+data['Width']/2. + currentTime = timeOps.time() + deltaTime = currentTime-data['StartTime'] + data['Height'] = data['MaxHeight']*math.sin(deltaTime/data['Period']*(math.pi*2)) + data['Location'] = "{x}>"+str(data['Left']) + ", " +\ + "{x}<"+str(data['Right'])+", {y}<" + str(data['Bottom']) + ",\ + {y}>"+str(data['Bottom']-data['Height']) + + ret.append(data) + return (ret, []) + + diff --git a/behaviors/RiseFall.xml b/behaviors/RiseFall.xml new file mode 100644 index 0000000..eaadd7b --- /dev/null +++ b/behaviors/RiseFall.xml @@ -0,0 +1,7 @@ + + behaviors.RiseFall + + 2000 + 50 + + diff --git a/behaviors/RunningBehavior.py b/behaviors/RunningBehavior.py index 5eb33f7..ab3dc80 100644 --- a/behaviors/RunningBehavior.py +++ b/behaviors/RunningBehavior.py @@ -3,6 +3,11 @@ import util.ComponentRegistry as compReg import util.Geo as Geo import pdb class RunningBehavior(Behavior): + """RunningBehavior is a straightforward behavior that makes a Location run back and forth across + a screen. Specify: + -- the length of movment in units when the response moves. + """ + def processResponse(self, sensorInputs, recursiveInputs): newResponses = sensorInputs ret = [] diff --git a/behaviors/Square.py b/behaviors/Square.py index aef3622..9d3223a 100644 --- a/behaviors/Square.py +++ b/behaviors/Square.py @@ -1,12 +1,17 @@ from operationscore.Behavior import * class Square(Behavior): - def processResponse(self, sensorInputs, recursiveInputs): - for sensory in sensorInputs:#TODO: consider replicating the dict - xLoc = sensory['Location'][0] - yLoc = sensory['Location'][1] - width = self['Width'] - #sensory['Location'] = 'True' - sensory['Location'] =\ - '{x}<'+str(xLoc+width)+',{x}>'+str(xLoc-width)+\ - ',{y}<'+str(yLoc+width)+',{y}>'+str(yLoc-width) - return (sensorInputs, recursiveInputs) + """Square is a simple behavior that makes a square with side lengths Width*2 around locations in + the sensor input. Specify: + -- the sidelength/2 + """ + + def processResponse(self, sensorInputs, recursiveInputs): + for sensory in sensorInputs:#TODO: consider replicating the dict + xLoc = sensory['Location'][0] + yLoc = sensory['Location'][1] + width = self['Width'] + #sensory['Location'] = 'True' + sensory['Location'] =\ + '{x}<'+str(xLoc+width)+',{x}>'+str(xLoc-width)+\ + ',{y}<'+str(yLoc+width)+',{y}>'+str(yLoc-width) + return (sensorInputs, recursiveInputs) diff --git a/behaviors/TimedDie.py b/behaviors/TimedDie.py new file mode 100644 index 0000000..e75e9dd --- /dev/null +++ b/behaviors/TimedDie.py @@ -0,0 +1,15 @@ +from operationscore.Behavior import * +class Timeout(Behavior): + """Timeout is a behavior designed to be used in recursive hooks to stop responses after a certain + amount of time. It is the Time-version of RecursiveDecay. Specify: + -- the time in ms that the response will run. + """ + + def processResponse(self, sensorInputs, recur): + ret = [] + for data in sensorInputs: + if not 'StartTime' in data: + data['StartTime'] = timeops.time() + if timeops.time()-data['StartTime'] < self['Timeout']: + ret.append(data) + return (ret, []) diff --git a/behaviors/Timeout.py b/behaviors/Timeout.py new file mode 100644 index 0000000..14d4873 --- /dev/null +++ b/behaviors/Timeout.py @@ -0,0 +1,16 @@ +from operationscore.Behavior import * +import util.TimeOps as timeops +class Timeout(Behavior): + """Timeout is a behavior designed to be used in recursive hooks to stop responses after a certain + amount of time. It is the Time-version of RecursiveDecay. Specify: + -- the time in ms that the response will run. + """ + + def processResponse(self,sensorInputs, recur): + ret = [] + for data in sensorInputs: + if not 'StartTime' in data: + data['StartTime'] = timeops.time() + if timeops.time()-data['StartTime'] < self['Timeout']: + ret.append(data) + return (ret,[]) diff --git a/behaviors/XYMove.py b/behaviors/XYMove.py index 8acbba8..11cee96 100644 --- a/behaviors/XYMove.py +++ b/behaviors/XYMove.py @@ -1,6 +1,13 @@ from operationscore.Behavior import * import util.Geo as Geo class XYMove(Behavior): + """XYMove is a behavior designed to be used as a recursive hook to ResponseMover to move pixels by + XStep and YStep. As XStep and YStep are maintained in the responses itself, they can be + modulated to facilitate, acceleration, modulation, bouncing, etc. Specify: + -- the starting XStep + -- the starting YStep + """ + def processResponse(self, sensor, recurs): ret = [] for loc in sensor: diff --git a/config/6thFloor.xml b/config/6thFloor.xml index c98486a..b58ff57 100644 --- a/config/6thFloor.xml +++ b/config/6thFloor.xml @@ -39,8 +39,17 @@ inputs.PygameInput - pygame + pygameclick 10 + True + + + + inputs.PygameInput + + pygamekey + 10 + True @@ -62,14 +71,11 @@ inputs/MouseFollower.xml - - inputs.RandomLocs - - randomLoc - - + + behaviors/RiseFall.xml + behaviors.EchoBehavior @@ -88,9 +94,6 @@ 1 - - behaviors/RedShift.xml - behaviors/RandomColor.xml @@ -159,21 +162,32 @@ + + behaviors.BehaviorChain + + risefalldie + + risefall + 2sec + dim + + + behaviors.BehaviorChain inpexpanddim - pygame + pygamekey + mitdoors colorchange mover decay - {'mover':'movebounce'} + {'mover':'risefalldie'} True - gaussmap @@ -182,11 +196,25 @@ debug 0 - pygame + pygamekey udp + + behaviors.Timeout + + 2sec + 2000 + + + + behaviors.MITDoors + + mitdoors + 50 + + behaviors.AllPixelsLeft @@ -211,7 +239,7 @@ runcolordecay - pygame + pygameclick colorchange @@ -223,39 +251,12 @@ gaussmap - - behaviors.BehaviorChain - - randomwalk - - pygame - - - colorchange - mover - decay - - {'mover':'redwalk'} - False - gaussmap - - behaviors.ResponseMover mover - - behaviors.BehaviorChain - - redwalk - - randmovement - redshift - - - behaviors.RandomWalk diff --git a/inputs/PygameInput.py b/inputs/PygameInput.py index 27b82b0..399a77e 100644 --- a/inputs/PygameInput.py +++ b/inputs/PygameInput.py @@ -6,18 +6,27 @@ from pygame.locals import * #This class processes input from an already running pygame instance and passes #it to the parent. This class requires an already running pygame instance. class PygameInput(Input): + """PygameInput is an input tied to the PygameDisplay. Specify: + True to receive an input every frame specifying the current mouse + position. + True to grab keystrokes + True to grab clicks. + + NB: If follow mouse is enabled, PygameInput will not return mouse and keypresses. You can, however, + instantiate other PygameInputs in the XML that will capture mouse and keypresses.""" def sensingLoop(self): - #try: - if self['FollowMouse']: - self.respond({Strings.LOCATION: pygame.mouse.get_pos()}) - return - for event in pygame.event.get(): - if event.type is KEYDOWN: - if event.key == 27: - self.die() - self.respond({Strings.LOCATION: (5,5),'Key': event.key}) - if event.type is MOUSEBUTTONDOWN: + if self['FollowMouse']: + self.respond({Strings.LOCATION: pygame.mouse.get_pos()}) + return + for event in pygame.event.get(): + if event.type is KEYDOWN: + if event.key == 27: + self.die() + if self['Keyboard']: + self.respond({'Key': event.key}) + return + else: + pygame.event.post(event) + if event.type is MOUSEBUTTONDOWN: + if self['Clicks']: self.respond({Strings.LOCATION: pygame.mouse.get_pos()}) - #except: - #raise Exception('Pygame not initialized. Pygame must be \ - #initialized.') diff --git a/inputs/RandomLocs.py b/inputs/RandomLocs.py index d1ce1c7..2719981 100644 --- a/inputs/RandomLocs.py +++ b/inputs/RandomLocs.py @@ -4,6 +4,9 @@ import util.Geo as Geo import util.Strings as Strings from operationscore.Input import * class RandomLocs(Input): + """RandomLocs is an Input that generates RandomLocations at a preset time interval. Just a + prototype, some assembly required.""" + def inputInit(self): self['LastEvent'] = clock.time() def sensingLoop(self): #TODO: move to params diff --git a/inputs/TCPInput.py b/inputs/TCPInput.py index 2bc69ef..17ea7e6 100644 --- a/inputs/TCPInput.py +++ b/inputs/TCPInput.py @@ -6,6 +6,10 @@ import logging as main_log import string class TCPInput(Input): + """TCPInput is a input to receive input on a TCP port. In its current incarnation, it parses + json data into python dicts. Warning: contains a bug where init will hang until it receives a + connection. Specify: + -- Port number to listen on.""" def inputInit(self): self.HOST = '' # Symbolic name meaning all available interfaces self.PORT = self.argDict['Port'] # Arbitrary non-privileged port @@ -34,12 +38,7 @@ class TCPInput(Input): for datagroup in data.split('\n'): if datagroup != None and datagroup != '': dataDict = json.loads(datagroup) - #print dataDict self.respond(dataDict) - #socketDict = {'data':dataDict, 'address':self.address} - #socketDict = {Strings.LOCATION: (dataDict['x'], dataDict['y'])} # like PygameInput - #print 'input' - #self.respond(socketDict) except Exception as exp: print str(exp) else: diff --git a/inputs/UDPInput.py b/inputs/UDPInput.py index e95bd33..83e2f77 100644 --- a/inputs/UDPInput.py +++ b/inputs/UDPInput.py @@ -1,6 +1,10 @@ from operationscore.Input import * import socket class UDPInput(Input): + """UDPInput is a barebones UDP Input class. It takes any data it receives and adds it to the + 'data' element of the response dict. It also notes the 'address'. Specify: + -- the Port to listen on.""" + def inputInit(self): HOST = '' # Symbolic name meaning all available interfaces PORT = self.argDict['Port'] # Arbitrary non-privileged port diff --git a/layouts/LineLayout.py b/layouts/LineLayout.py index 34f5e36..1d40105 100644 --- a/layouts/LineLayout.py +++ b/layouts/LineLayout.py @@ -1,5 +1,5 @@ from operationscore.PixelAssembler import * -#Simple layout class that simply makes a line of LEDs class LineLayout(PixelAssembler): + """LineLayout is a layout class that makes a line of LEDs""" def layoutFunc(self, lastLocation): return (lastLocation[0]+self.argDict['spacing'], lastLocation[1]) diff --git a/layouts/ZigzagLayout.py b/layouts/ZigzagLayout.py index 221571c..3fc2ea1 100644 --- a/layouts/ZigzagLayout.py +++ b/layouts/ZigzagLayout.py @@ -10,6 +10,15 @@ import pdb # | # X-X-X-X etc. class ZigzagLayout(PixelAssembler): + """ZigZagLayout is a slightly more complex layout class that makes a zig-Zag Led Pattern + Inheriting classes must specify zigLength, the length in lights of a of a zig + and zig Axis, the direction of the long X axis (X or Y). + EG: zig length = 4, zig Axis = X would give: + X-X-X-X + | + X-X-X-X + | + X-X-X-X etc. def initLayout(self): if not 'zigLength' in self.argDict: raise Exception('zigLength must be defined in argDict') diff --git a/operationscore/Behavior.py b/operationscore/Behavior.py index b3f7342..6424842 100644 --- a/operationscore/Behavior.py +++ b/operationscore/Behavior.py @@ -1,11 +1,3 @@ -#Abstract class for a behavior. On every time step, the behavior is passed the -#inputs from all sensors it is bound to as well as any recursive inputs that it -#spawned during the last time step. Inheriting classes MUST define -#processResponse. processResponse should return a list of dictionaries which -#define the properties of the light response, (outputs, recursions). They must give a location and -#color. They may define a PixelEvent to more closely control the outgoing -#data, however, this is normally handled by routing the event to a behavior -#specifically designed to do this (like DecayBehavior). import pdb from operationscore.SmootCoreObject import * @@ -13,6 +5,16 @@ from logger import main_log #timeStep is called on every iteration of the LightInstallation #addInput is called on each individual input received, and the inputs queue class Behavior(SmootCoreObject): + """Abstract class for a behavior. On every time step, the behavior is passed the + inputs from all sensors it is bound to as well as any recursive inputs that it + spawned during the last time step. Inheriting classes MUST define + processResponse. processResponse should return a list of dictionaries which + define the properties of the light response, (outputs, recursions). They must give a location and + color. They may define a PixelEvent to more closely control the outgoing + data, however, this is normally handled by routing the event to a behavior + specifically designed to do this (like AddPixelEvent). + timeStep is called on every iteration of the LightInstallation + addInput is called on each individual input received, and the inputs queue""" def init(self): self.validateArgs('Behavior.params') if type(self['Inputs']) != type([]): diff --git a/operationscore/Input.py b/operationscore/Input.py index 69375d3..d3d5644 100644 --- a/operationscore/Input.py +++ b/operationscore/Input.py @@ -1,16 +1,14 @@ import threading,time from logger import main_log, exception_log from operationscore.ThreadedSmootCoreObject import ThreadedSmootCoreObject -#Abstract class for inputs. Inheriting classes should call "respond" to raise -#their event. Inheriting classes MUST define sensingLoop. Called at the -#interval specified in RefreshInterval while the input is active. For example, if you are writing -#webserver, this is where the loop should go. -#Inheriting classes MAY define inputInit. This is called before the loop -#begins. import pdb class Input(ThreadedSmootCoreObject): - #Event scope is a function pointer the function that will get called when - #an Parent is raised. + """Abstract class for inputs. Inheriting classes should call "respond" to raise + their event. Inheriting classes MUST define sensingLoop. Called at the + interval specified in RefreshInterval while the input is active. For example, if you are writing + webserver, this is where the loop should go. + Inheriting classes MAY define inputInit. This is called before the loop + begins.""" def init(self): self.eventQueue = [] if not 'RefreshInterval' in self.argDict: diff --git a/operationscore/PixelEvent.py b/operationscore/PixelEvent.py index c41df17..5ed6163 100644 --- a/operationscore/PixelEvent.py +++ b/operationscore/PixelEvent.py @@ -1,6 +1,6 @@ -#Class defining a light response. Inheriting classes should define lightState, -#which should return a color, or None if the response is complete. Consider -#requiring a generate event. +"""PixelEvent is a class defining a light response. Inheriting classes should define state, +which should return a color, or None if the response is complete. Consider +requiring a generate event.""" from operationscore.SmootCoreObject import * from pixelevents.StepEvent import * import util.ColorOps as color @@ -26,6 +26,5 @@ class PixelEvent(SmootCoreObject): color = responseDict['Color'] else: raise Exception('Need Color. Probably') - pdb.set_trace() responseDict['PixelEvent'] = StepEvent.generate(300, color) diff --git a/operationscore/PixelMapper.py b/operationscore/PixelMapper.py index 7e2b0af..3937973 100644 --- a/operationscore/PixelMapper.py +++ b/operationscore/PixelMapper.py @@ -2,6 +2,9 @@ from operationscore.SmootCoreObject import * from logger import main_log import pdb class PixelMapper(SmootCoreObject): + """PixelMapper is the parent class for PixelMappers. Inheriting classes should define + mappingFunction which takes an eventLocation and a screen and returns a list of (weight, pixels). PixelMapper + handles caching automatically.""" def init(self): self.mem = {} #Dictionary of all seen events self.totalCalls = 0 @@ -19,6 +22,7 @@ class PixelMapper(SmootCoreObject): return self.mem[eventLocation] #Takes a Screen and returns a list of tuples #(pixel, weight), with the sum of weights = 1 - #TODO: consider abstracting away from pixels def mappingFunction(self,eventLocation, screen): - pass + """Takes a Screen and event location and returns a list of tuples (pixel,weight) with + sum(weights)=1""" + raise Exception('Mapping function not defined!') diff --git a/operationscore/Renderer.py b/operationscore/Renderer.py index b422304..0861c44 100644 --- a/operationscore/Renderer.py +++ b/operationscore/Renderer.py @@ -1,10 +1,10 @@ -#Renderer abstract class. Doesn't do much now, but might do more later. -#Inheriting classes MUST define render which takes a light system and renders it. -#Inheriting classes may define initRenderer which is called after the dictionary -#is pulled from config. #TODO: multithreaded-rendering from operationscore.SmootCoreObject import * class Renderer(SmootCoreObject): + """Renderer abstract class. Doesn't do much now, but might do more later. + Inheriting classes MUST define render which takes a light system and renders it. + Inheriting classes may define initRenderer which is called after the dictionary + is pulled from config.""" def init(self): self.initRenderer() def render(lightSystem): diff --git a/operationscore/SmootCoreObject.py b/operationscore/SmootCoreObject.py index 8b36f4d..0d32773 100644 --- a/operationscore/SmootCoreObject.py +++ b/operationscore/SmootCoreObject.py @@ -4,6 +4,10 @@ import thread import util.Config as configGetter class SmootCoreObject(object): + """SmootCoreObject is essentially a super-object class which grants us some niceties. It allows + us to use objects as if they are dictionaries -- we use this to store their arguments + convienently -- note that querying for a parameter that does not exist will return None. It + also offers some basic ThreadSafety.""" def __init__(self, argDict, skipValidation = False): self.dieListeners = [] self.argDict = argDict @@ -13,8 +17,6 @@ class SmootCoreObject(object): for key in argDict: setattr(self, key, argDict[key]) self.init() #call init of inheriting class - # self.__setitem__ = self.argDict.__setitem__ - # self.__getitem__ = self.argDict.__getitem__ def init(self): pass @@ -37,7 +39,8 @@ class SmootCoreObject(object): return self.argDict[item] else: return None - + def __contains__(self, item): + return item in self.argDict def __getiter__(self): return self.argDict.__getiter__() diff --git a/operationscore/ThreadedSmootCoreObject.py b/operationscore/ThreadedSmootCoreObject.py index 967ee35..4b5d513 100644 --- a/operationscore/ThreadedSmootCoreObject.py +++ b/operationscore/ThreadedSmootCoreObject.py @@ -4,6 +4,8 @@ import thread import util.Config as configGetter from operationscore.SmootCoreObject import SmootCoreObject class ThreadedSmootCoreObject(SmootCoreObject, threading.Thread): + """ThreadedSmootCoreObject is a version of SmootCoreObject for objects that want to run on their + own thread""" def __init__(self, argDict, skipValidation = False): SmootCoreObject.__init__(self, argDict, skipValidation) threading.Thread.__init__(self) diff --git a/pixelcore/Pixel.py b/pixelcore/Pixel.py index 2f21fd8..6ff2e67 100644 --- a/pixelcore/Pixel.py +++ b/pixelcore/Pixel.py @@ -2,12 +2,13 @@ import util.ColorOps as color import pdb from pixelevents.StepEvent import * import util.TimeOps as timeops -#Pixel keeps a queue of events (PixelEvent objects) (actually a dictionary -#keyed by event time). Every time is state is -#requested, it processes all the members of its queue. If a member returns none, -#it is removed from the queue. Otherwise, its value added to the Pixels color -#weighted by z-index. class Pixel: + """Pixel keeps a queue of events (PixelEvent objects) (actually a dictionary + keyed by event time). Every time is state is + requested, it processes all the members of its queue. If a member returns none, + it is removed from the queue. Otherwise, its value added to the Pixels color + weighted by z-index.""" + radius = 2 timeOff = -1 @@ -24,26 +25,22 @@ class Pixel: #processInput instead. Also, you shouldn't use this anyway. You should be #using the input method on the screen! def turnOnFor(self, time): - event = StepEvent.generate(time, (255,255,255)) #TODO: Move color to + event = StepEvent.generate(time, (255,255,255)) self.processInput(event, 0) - #arg #Add a pixelEvent to the list of active events def processInput(self,pixelEvent,zindex, scale=1,currentTime=None): #consider migrating arg to dict - #TODO: fix for multiple pixel events if currentTime == None: currentTime = timeops.time() - #if not currentTime in self.events: - # self.events[currentTime] = [] - #self.events[currentTime].append((zindex,scale, pixelEvent)) - self.events.append((currentTime, zindex, scale, pixelEvent)) #TODO: this is kindof - #gross + self.events.append((currentTime, zindex, scale, pixelEvent)) #TODO: clean this up, maybe? def clearAllEvents(self): self.events = [] - #Combines all PixelEvents currently active and computes the current color of - #the pixel. - def state(self, currentTime=timeops.time()): #TODO: this only evaluates at import time, I think + def state(self, currentTime=None): + """Combines all PixelEvents currently active and computes the current color of + the pixel.""" + if currentTime == None: + currentTime = timeops.time() if currentTime-self.lastRenderTime < 5: return self.lastRender if self.events == []: @@ -54,8 +51,7 @@ class Pixel: colors = [] for eventObj in self.events: #TODO: right color weighting code if len(self.events) > 50: - pdb.set_trace() - #TODO: this sucks. fix it + main_log.error('High pixel event count! Investigate!') eventTime, zindex, scale, pixelEvent = eventObj eventResult = pixelEvent.state(currentTime-eventTime) if eventResult != None: diff --git a/pixelcore/PixelEventManager.py b/pixelcore/PixelEventManager.py deleted file mode 100644 index 779a0ce..0000000 --- a/pixelcore/PixelEventManager.py +++ /dev/null @@ -1,2 +0,0 @@ -class PixelEventManager(object): - def init(self) diff --git a/pixelcore/PixelStrip.py b/pixelcore/PixelStrip.py index 662b8fe..595ce72 100644 --- a/pixelcore/PixelStrip.py +++ b/pixelcore/PixelStrip.py @@ -4,36 +4,21 @@ import util.Geo as Geo from pixelevents.StepEvent import * import math import pdb -#Python class representing a single Pixel strip (usually 50 Pixels) class PixelStrip: + """Python class representing a single Pixel strip (usually 50 Pixels)""" + def __init__(self, layoutEngine): self.initStrip(layoutEngine) self.argDict = layoutEngine.getStripArgs() + def initStrip(self, layoutEngine): pixelLocations = layoutEngine.getPixelLocations() self.pixels = [Pixel(l) for l in pixelLocations] + def __iter__(self): return self.pixels.__iter__() - def render(self, surface): - [l.render(surface) for l in self.pixels] - #step - def allOn(self, time): - [l.turnOnFor(time) for l in self.pixels] #TODO: add test-on method to - #pixels - def respond(self, responseInfo): - location = responseInfo[Strings.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) - (dist, pixel) = self.getPixelNearest(location) - pixel.processInput(responseInfo['PixelEvent'], 0) #TODO: z-index + + + - def getPixelNearest(self, location): - dists = [(Geo.dist(location, pixel.location), pixel) for pixel in self.pixels] - dists.sort() - return dists[0] - #just for now. diff --git a/pixelcore/Screen.py b/pixelcore/Screen.py index b595847..9a81df7 100644 --- a/pixelcore/Screen.py +++ b/pixelcore/Screen.py @@ -10,9 +10,10 @@ import itertools import sys import pdb from logger import main_log -#Class representing a collection of Pixels grouped into PixelStrips. Needs a -#PixelMapper, currently set via setMapper by may be migrated into the argDict. class Screen: + """Class representing a collection of Pixels grouped into PixelStrips. Needs a + PixelMapper, currently set via setMapper by may be migrated into the argDict.""" + def __init__(self): self.responseQueue = [] self.pixelStrips = [] @@ -20,14 +21,15 @@ class Screen: self.xPixelLocs = [] sizeValid = False self.pixelsSorted = False + def addStrip(self, lS): self.pixelStrips.append(lS) self.sizeValid = False #keep track of whether or not our screen size has self.pixelsSorted = False #been invalidated by adding more pixels - #Returns (pixelIndex, pixel). Does a binary search. def pixelsInRange(self, minX, maxX): + """Returns (pixelIndex, pixel). Does a binary search.""" if not self.pixelsSorted: self.computeXSortedPixels() minIndex = Search.find_ge(self.xPixelLocs, minX) @@ -41,10 +43,7 @@ class Screen: self.xSortedPixels.sort() self.xPixelLocs = [p[0] for p in self.xSortedPixels] self.pixelsSorted = True - #For debug only - def allOn(self): - [lS.allOn(-1) for lS in self.pixelStrips] - + def __iter__(self): #the iterator of all our pixel strips chained togther return itertools.chain(*[strip.__iter__() for strip in \ self.pixelStrips]) #the * operator breaks the list into args diff --git a/pixelevents/DecayEvent.py b/pixelevents/DecayEvent.py index f8d5ef0..8ff423c 100644 --- a/pixelevents/DecayEvent.py +++ b/pixelevents/DecayEvent.py @@ -3,6 +3,10 @@ import math from util.ColorOps import * import util.Geo as Geo class DecayEvent(PixelEvent): + """DecayEvent is a pixel event that can decay either Exponentially or Proportionally. Specify: + -- Exponential or Proportional + -- Controls the speed of decay.""" + def initEvent(self): self.coefficient = float(abs(self.Coefficient)) if self.DecayType == 'Exponential': diff --git a/pixelevents/SingleFrameEvent.py b/pixelevents/SingleFrameEvent.py index 767f403..252e31b 100644 --- a/pixelevents/SingleFrameEvent.py +++ b/pixelevents/SingleFrameEvent.py @@ -1,5 +1,8 @@ from operationscore.PixelEvent import * class SingleFrameEvent(PixelEvent): + """SingleFrameEvent is a PixelEvent that will only render for the first frame on which it is + queried""" + def initEvent(self): self.timeState = -1 def state(self, timeDelay): diff --git a/pixelmappers/GaussianMapper.py b/pixelmappers/GaussianMapper.py index c82f243..883a95d 100644 --- a/pixelmappers/GaussianMapper.py +++ b/pixelmappers/GaussianMapper.py @@ -1,8 +1,16 @@ from operationscore.PixelMapper import * import util.Geo as Geo class GaussianMapper(PixelMapper): + """GaussianMapper is a PixelMapper which weights pixels around an event proportional to a + gaussian surface. Specify: + -- The height of the gaussian surface + -- The width of the gaussian surface + -- the minimum weight event that can be returned + -- the maximum radius considered + """ + def mappingFunction(self, eventLocation, screen): - returnPixels = [] #TODO: consider preallocation and trimming + returnPixels = [] [x,y] = eventLocation potentialPixels = screen.pixelsInRange(x-self.CutoffDist, \ x+self.CutoffDist) diff --git a/pixelmappers/SimpleMapper.py b/pixelmappers/SimpleMapper.py index 19d44c4..1568de3 100644 --- a/pixelmappers/SimpleMapper.py +++ b/pixelmappers/SimpleMapper.py @@ -2,6 +2,10 @@ from operationscore.PixelMapper import * import util.Geo as Geo import sys class SimpleMapper(PixelMapper): + """SimpleMapper is a PixelMapper which maps events to the nearest Pixel. It also supports + strings of the form: + {x}>5, {y}<10, {x}*{y}<{x}, etc. (Conditons, separated by commas. and and or may also be + used).""" def mappingFunction(self, eventLocation, screen): if type(eventLocation) == type(tuple()): bestDist = sys.maxint @@ -19,7 +23,6 @@ class SimpleMapper(PixelMapper): return [] else: #{x}>5,{y}1) for i in range(4)]) == 4 + def addLocations(l1,l2): return tuple([l1[i]+l2[i] for i in range(len(l1))]) + def gaussian(x,height,center,width): a=height b=center c=width return a*math.exp(-((x-b)**2)/(2*c**2)) + def dist(l1, l2): - return math.sqrt((l1[0]-l2[0])**2+(l1[1]-l2[1])**2) - #return math.sqrt(sum([(l1[i]-l2[i])**2 for i in range(len(l1))])) + return math.sqrt((l1[0]-l2[0])**2+(l1[1]-l2[1])**2) #For speed + def randomLoc(boundingBox): #TODO: make less shitty loc = [] loc.append(random.randint(0, boundingBox[0])) loc.append(random.randint(0, boundingBox[1])) return tuple(loc) + def approxexp(x): + """Approximates exp with a 3 term Taylor Series.""" return 1+x+x**2/2+x**3/6 diff --git a/util/NetworkOps.py b/util/NetworkOps.py index 6c50c6d..8894b78 100644 --- a/util/NetworkOps.py +++ b/util/NetworkOps.py @@ -8,4 +8,3 @@ def getConnectedSocket(ip,port): except Exception as inst: main_log.error('Network down. All network based renderers and sensors will not function.', inst) - print (ip, port) diff --git a/util/PacketComposition.py b/util/PacketComposition.py index c4fcdc3..7b4fe95 100644 --- a/util/PacketComposition.py +++ b/util/PacketComposition.py @@ -6,6 +6,7 @@ UNI = 0 import pdb import util.TimeOps as timeops argDict = {'flags': 0, 'startcode': 0, 'pad':0} + def composePixelStripData(pixelStrip,currentTime=timeops.time()): packet = bytearray() for light in pixelStrip: @@ -19,6 +20,7 @@ def composePixelStripData(pixelStrip,currentTime=timeops.time()): #color = pixelStrip.pixels[i].state() #packet[i:i+2] = color # return bytearray(packet) + cache = {} def memoize(f): def helper(x): @@ -26,6 +28,7 @@ def memoize(f): cache[x] = f(x) return cache[x] return helper + @memoize def cachePacketHeader(port): packet = bytearray() @@ -35,11 +38,13 @@ def cachePacketHeader(port): packet.extend(portOutPacket(subDict)) packet.append(0x0) return packet + def composePixelStripPacket(pixelStrip,port, currentTime): packet = bytearray(cachePacketHeader(port)) data = composePixelStripData(pixelStrip, currentTime) packet.extend(data) return packet + def packheader(): header = bytearray() header.extend(struct.pack('L', MAGIC)) @@ -47,18 +52,20 @@ def packheader(): header.extend(struct.pack('H', PORTOUT)) header.extend(struct.pack('L', 0)) return header + def portOut(): header = packheader() header.extend(struct.pack('L', UNI)) return header + def portOutPayload(argDict): payload = bytearray() payload.extend(struct.pack('B', argDict['port'])) payload.extend(struct.pack('H', argDict['flags'])) - #payload.append(0x00) #somepadding? lolwtf. payload.extend(struct.pack('H', argDict['len'])) payload.extend(struct.pack('H', argDict['startcode'])) return payload + def portOutPacket(payloadArgs): packet = bytearray() packet.extend(portOut()) diff --git a/util/Search.py b/util/Search.py index f7e4b81..1499e23 100644 --- a/util/Search.py +++ b/util/Search.py @@ -1,13 +1,13 @@ from bisect import * def find_le(a, x): - 'Find rightmost value less than or equal to x' + """Find rightmost value less than or equal to x""" return bisect_right(a, x)-1 def find_ge(a, x): - 'Find leftmost value greater than x' + """Find leftmost value greater than x""" return bisect_left(a, x) -#returns parents of nodes that meed a given condition def parental_tree_search(root, childrenstr, conditionstr): + """Returns parents of nodes that meed a given condition""" ret = [] queue = [root] while queue: diff --git a/util/TimeOps.py b/util/TimeOps.py index dcd5038..841efab 100644 --- a/util/TimeOps.py +++ b/util/TimeOps.py @@ -1,6 +1,7 @@ import time as clock def time(): return clock.time()*1000 #all times in MS + class Stopwatch: def __init__(self): self.running = False -- cgit v1.2.3