diff options
author | rcoh <rcoh@mit.edu> | 2011-02-12 15:59:59 -0500 |
---|---|---|
committer | rcoh <rcoh@mit.edu> | 2011-02-12 15:59:59 -0500 |
commit | 4d62e1152241854ab864142d827d735d84405078 (patch) | |
tree | 2589c6a37721e1b8362c8e8c6a580c8c185b2f86 | |
parent | b45b9079c5decd720d8275378bb0d6dc172c6234 (diff) |
Modified the test code to automatically pick up new tests. Just add your test file to
tests/__init__.py. Fixed a bug in ZigZagLayout. Added the BehaviorQuerySystem to allow querying
on behaviors. Added the "ModulateColor" behavior which will continually shift a color along the HSV
plane. Added TestBQS to test the behavior query system. Modified Behavior to have a getLastOutput
and setLastOutput method.
-rw-r--r-- | TestAll.py | 8 | ||||
-rw-r--r-- | behaviors/ColorShift.py | 16 | ||||
-rw-r--r-- | behaviors/ModulateColor.py | 16 | ||||
-rw-r--r-- | config/C5Demo.xml | 231 | ||||
-rw-r--r-- | config/Demo.xml | 9 | ||||
-rw-r--r-- | layouts/C5SignLayout.xml | 16 | ||||
-rw-r--r-- | layouts/ZigzagLayout.py | 11 | ||||
-rw-r--r-- | operationscore/Behavior.py | 6 | ||||
-rw-r--r-- | operationscore/SmootCoreObject.py | 2 | ||||
-rw-r--r-- | tests/TestBQS.py | 18 | ||||
-rw-r--r-- | tests/__init__.py | 3 | ||||
-rw-r--r-- | util/BehaviorQuerySystem.py | 39 | ||||
-rw-r--r-- | util/ComponentRegistry.py | 25 | ||||
-rw-r--r-- | util/Geo.py | 5 |
14 files changed, 377 insertions, 28 deletions
@@ -1,9 +1,7 @@ import unittest from unittest import TestLoader -import tests.TestConfigLoaders -import tests.TestComponentRegistry -testSuite = TestLoader().loadTestsFromModule(tests.TestConfigLoaders) -unittest.TextTestRunner(verbosity=2).run(testSuite) +import tests -testSuite = TestLoader().loadTestsFromModule(tests.TestComponentRegistry) +testSuite = TestLoader().loadTestsFromModule(tests) unittest.TextTestRunner(verbosity=2).run(testSuite) + diff --git a/behaviors/ColorShift.py b/behaviors/ColorShift.py new file mode 100644 index 0000000..f185b40 --- /dev/null +++ b/behaviors/ColorShift.py @@ -0,0 +1,16 @@ +import util.ColorOps as colorOps +from operationscore.Behavior import * +import colorsys +class ColorShift(Behavior): + def processResponse(self, sensor, recurs): + ret = [] + for data in sensor: + if not 'HSV' in data: + data['HSV'] = list(colorsys.rgb_to_hsv(*data['Color'])) + + data['HSV'][0] += .01 + if data['HSV'][0] >= 360: + data['HSV'][0] = 0 + data['Color'] = colorsys.hsv_to_rgb(*data['HSV']) + ret.append(data) + return (ret,[]) diff --git a/behaviors/ModulateColor.py b/behaviors/ModulateColor.py new file mode 100644 index 0000000..904526e --- /dev/null +++ b/behaviors/ModulateColor.py @@ -0,0 +1,16 @@ +import util.ColorOps as colorOps +from operationscore.Behavior import * +import colorsys +class ColorShift(Behavior): + def processResponse(self, sensor, recurs): + ret = [] + for data in sensor: + if not 'HSV' in data: + data['HSV'] = colorsys.rgb_to_hsv(data['Color']) + + data['HSV'][0] += .5 + if data['HSV'][0] >= 360: + data['HSV'][0] = 0 + data['Color'] = colorsys.hsv_to_rgb(data['HSV']) + ret.append(data) + return (ret,[]) diff --git a/config/C5Demo.xml b/config/C5Demo.xml new file mode 100644 index 0000000..c0b4402 --- /dev/null +++ b/config/C5Demo.xml @@ -0,0 +1,231 @@ +<!---All configuration items contain a "Class" tag specifying the python class they represent, and an "Args" tag specifying the args to be passed in.--> +<LightInstallation> + <InstallationConfiguration> + <Defaults> + <PixelMapper>simplemap</PixelMapper> + </Defaults> + </InstallationConfiguration> + <PixelConfiguration> + <InheritsFrom>layouts/C5SignLayout.xml</InheritsFrom> + </PixelConfiguration> + <PixelMapperConfiguration> + <PixelMapper> + <Class>pixelmappers.SimpleMapper</Class> + <Args> + <Id>simplemap</Id> + <CutoffDist>20</CutoffDist> + </Args> + </PixelMapper> + <PixelMapper> + <Class>pixelmappers.GaussianMapper</Class> + <Args> + <Id>gaussmap</Id> + <CutoffDist>30</CutoffDist> + <MinWeight>0.1</MinWeight> + <Width>10</Width> + <Height>1</Height> + </Args> + </PixelMapper> + </PixelMapperConfiguration> + <RendererConfiguration> + <Renderer> + <InheritsFrom>renderers/Pygame.xml</InheritsFrom> + </Renderer> + </RendererConfiguration> + <InputConfiguration> + <InputElement> + <Class>inputs.PygameInput</Class> + <Args> + <Id>pygameclick</Id> + <RefreshInterval>10</RefreshInterval> + <Clicks>True</Clicks> + </Args> + </InputElement> + <InputElement> + <Class>inputs.PygameInput</Class> + <Args> + <Id>pygamekey</Id> + <RefreshInterval>10</RefreshInterval> + <Keyboard>True</Keyboard> + </Args> + </InputElement> + <InputElement> + <Class>inputs.UDPInput</Class> + <Args> + <Id>udp</Id> + <Port>3344</Port> + <RefreshInterval>50</RefreshInterval> + </Args> + </InputElement> + <!--<InputElement> + <Class>inputs.TCPInput</Class> + <Args> + <Id>tcp</Id> + <Port>20120</Port> + <RefreshInterval>10</RefreshInterval> + </Args> + </InputElement>--> + <InputElement Id="followmouse" RefreshInterval="1000"> + <InheritsFrom>inputs/MouseFollower.xml</InheritsFrom> + </InputElement> + </InputConfiguration> + <BehaviorConfiguration> + <Behavior Id="colorchange"> + <InheritsFrom>behaviors/RandomColor.xml</InheritsFrom> + <Args> + <ColorList> + <Val>(255,0,0)</Val> + <Val>(0,0,255)</Val> + </ColorList> + </Args> + </Behavior> + <Behavior Id="decay"> + <InheritsFrom>behaviors/PixelDecay.xml</InheritsFrom> + </Behavior> + <Behavior Id="singleframe"> + <InheritsFrom>behaviors/SingleFrame.xml</InheritsFrom> + </Behavior> + <Behavior Id="slowdecay"> + <InheritsFrom>behaviors/PixelDecay.xml</InheritsFrom> + <Args> + <Coefficient>.01</Coefficient> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.XYMove</Class> + <Args> + <Id>xymove</Id> + <XStep>5</XStep> + <YStep>2</YStep> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.RestrictLocation</Class> + <Args> + <Id>xbounce</Id> + <Action>{val}*-1</Action> + <ParamName>XStep</ParamName> + <LocationRestriction>{x}<0 or {x}>200</LocationRestriction> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.RestrictLocation</Class> + <Args> + <Id>ybounce</Id> + <Action>{val}*-1</Action> + <ParamName>YStep</ParamName> + <LocationRestriction>{y}<0 or {y}>100</LocationRestriction> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.BehaviorChain</Class> + <Args> + <Id>movebounce</Id> + <ChainedBehaviors> + <Id>xymove</Id> + <Id>colorshift</Id> + <Id>ybounce</Id> + <Id>xbounce</Id> + </ChainedBehaviors> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.ModifyParam</Class> + <Args> + <Id>ysin</Id> + <ParamName>YStep</ParamName> + <ParamType>Sensor</ParamType> + <ParamOp>4*math.sin({x}/float(40))</ParamOp> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.DebugBehavior</Class> + <Args> + <Id>debug</Id> + <z-index>0</z-index> + <Inputs> + <Id>pygamekey</Id> + <Id>udp</Id> + </Inputs> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.Square</Class> + <Args> + <Id>square</Id> + <Width>20</Width> + </Args> + </Behavior> + <Behavior Id="recursivedecay"> + <InheritsFrom>behaviors/LoopAndDie.xml</InheritsFrom> + <Args> + <InitialResponseCount>80</InitialResponseCount> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.BehaviorChain</Class> + <Args> + <Id>runcolordecay</Id> + <Inputs> + <Id>pygameclick</Id> + </Inputs> + <ChainedBehaviors> + <Id>colorchange</Id> + <Id>mover</Id> + <Id>decay</Id> + </ChainedBehaviors> + <RecursiveHooks>{'mover':'movebounce'}</RecursiveHooks> + <RenderToScreen>True</RenderToScreen> + <Mapper>gaussmap</Mapper> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.ResponseMover</Class> + <Args> + <Id>mover</Id> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.RandomWalk</Class> + <Args> + <Id>randmovement</Id> + <StepSize>2</StepSize> + </Args> + </Behavior> + <Behavior Id="accelerate"> + <InheritsFrom>behaviors/Accelerate.xml</InheritsFrom> + </Behavior> + <Behavior> + <Class>behaviors.EchoBehavior</Class> + <Args> + <Id>echo</Id> + <z-index>0</z-index> + <RenderToScreen>False</RenderToScreen> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.ColorShift</Class> + <Args> + <Id>colorshift</Id> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.BehaviorChain</Class> + <Args> + <Id>mousechaser</Id> + <Inputs> + <Id>followmouse</Id> + </Inputs> + <ChainedBehaviors> + <Id>echo</Id> + <Id>square</Id> + <Id>singleframe</Id> + </ChainedBehaviors> + <RenderToScreen>True</RenderToScreen> + </Args> + </Behavior> + <Behavior Id="running"> + <InheritsFrom>behaviors/RunningBehavior.xml</InheritsFrom> + </Behavior> + </BehaviorConfiguration> +</LightInstallation> diff --git a/config/Demo.xml b/config/Demo.xml index 4e811ba..67e9811 100644 --- a/config/Demo.xml +++ b/config/Demo.xml @@ -126,6 +126,7 @@ <Id>movebounce</Id> <ChainedBehaviors> <Id>xymove</Id> + <Id>colorshift</Id> <Id>ybounce</Id> <Id>xbounce</Id> </ChainedBehaviors> @@ -206,6 +207,12 @@ </Args> </Behavior> <Behavior> + <Class>behaviors.ColorShift</Class> + <Args> + <Id>colorshift</Id> + </Args> + </Behavior> + <Behavior> <Class>behaviors.BehaviorChain</Class> <Args> <Id>mousechaser</Id> @@ -217,7 +224,7 @@ <Id>square</Id> <Id>singleframe</Id> </ChainedBehaviors> - <RenderToScreen>True</RenderToScreen> + <RenderToScreen>False</RenderToScreen> </Args> </Behavior> <Behavior Id="running"> diff --git a/layouts/C5SignLayout.xml b/layouts/C5SignLayout.xml new file mode 100644 index 0000000..58310d7 --- /dev/null +++ b/layouts/C5SignLayout.xml @@ -0,0 +1,16 @@ + <PixelConfiguration> + <PixelStrip> + <Class>layouts.ZigzagLayout</Class> + <Args> + <Id>strip1</Id> + <zigLength>10</zigLength> + <zigAxis>X</zigAxis> + <yDirection>1</yDirection> + <pixelToPixelSpacing>20</pixelToPixelSpacing> + <spacing>20</spacing> <!--we can space at any value less the + l2lspacing--> + <numPixels>50</numPixels> + <originLocation>(0,0)</originLocation> + </Args> + </PixelStrip> + </PixelConfiguration> diff --git a/layouts/ZigzagLayout.py b/layouts/ZigzagLayout.py index 3fc2ea1..665d14e 100644 --- a/layouts/ZigzagLayout.py +++ b/layouts/ZigzagLayout.py @@ -1,14 +1,5 @@ from operationscore.PixelAssembler import * import pdb -#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. 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 @@ -18,7 +9,7 @@ class ZigzagLayout(PixelAssembler): | X-X-X-X | - X-X-X-X etc. + 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 97fa020..7090a23 100644 --- a/operationscore/Behavior.py +++ b/operationscore/Behavior.py @@ -19,6 +19,7 @@ class Behavior(SmootCoreObject): self.recursiveResponseQueue = [] self.sensorResponseQueue = [] self.outGoingQueue = [] + self.lastState = None self.behaviorInit() def behaviorInit(self): pass @@ -44,7 +45,9 @@ class Behavior(SmootCoreObject): def getLastOutput(self): return self.lastState def setLastOutput(self, output): - """Override to modify state.""" + """Override to modify state. For example: if you are using a behavior that does uses + strings for location specification, you will want to override this to point to a single + location. Make sure you keep lastState as a [] of {}. (List of dicts)""" self.lastState = output def addMapperToResponse(self, responses): if self['Mapper'] != None: @@ -61,5 +64,6 @@ class Behavior(SmootCoreObject): self.recursiveResponseQueue) self.sensorResponseQueue = [] self.recursiveResponseQueue = recursions + self.setLastOutput(outputs) main_log.debug(self['Id'] + ' Ouputs ' + str(outputs)) return self.addMapperToResponse(outputs) diff --git a/operationscore/SmootCoreObject.py b/operationscore/SmootCoreObject.py index 51b84e3..6addb9c 100644 --- a/operationscore/SmootCoreObject.py +++ b/operationscore/SmootCoreObject.py @@ -40,6 +40,8 @@ class SmootCoreObject(object): item = self.argDict[key] if isinstance(item, types.FunctionType): return item(self.argDict) #resolve the lambda function, if it exists + #elif isinstance(item, list): #if its a list of items + # pass #TODO: consider doing resolution of lambda funcs for items in lists else: return item else: diff --git a/tests/TestBQS.py b/tests/TestBQS.py new file mode 100644 index 0000000..8dc90b2 --- /dev/null +++ b/tests/TestBQS.py @@ -0,0 +1,18 @@ +import unittest +import util.BehaviorQuerySystem as bqs +from behaviors.ColorChangerBehavior import * +class TestBQS(unittest.TestCase): + def setUp(self): + bqs.initBQS() + b = ColorChangerBehavior({'Id': 'color','ColorList':[(255,0,0)]}) + bqs.addBehavior(b) + b.addInput({'Location':(5,5)}) + b.timeStep() + def tearDown(self): + bqs.initBQS() + + def test_color_predicate(self): + validQuery = lambda args:args['Color']==(255,0,0) + invalidQuery = lambda args:args['Color']==(254,0,0) + assert bqs.query(validQuery) == [{'Color':(255,0,0), 'Location':(5,5)}] + assert bqs.query(invalidQuery) == [] diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..0365616 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,3 @@ +from TestComponentRegistry import TestComponentRegistry +from TestConfigLoaders import TestConfigLoaders +from TestBQS import TestBQS diff --git a/util/BehaviorQuerySystem.py b/util/BehaviorQuerySystem.py new file mode 100644 index 0000000..5399435 --- /dev/null +++ b/util/BehaviorQuerySystem.py @@ -0,0 +1,39 @@ +import types +"""The behavior query system is a module that allows querying behaviors based on lambda-function +predicates.""" +def initBQS(): + global behaviorList, initialized + behaviorList = [] + initialized = True + +def addBehavior(behavior): + behaviorList.append(behavior) + +def query(predicateList): + """BehaviorQuerySystem.query takes a list of predicates (functions with signature: + (behavior,output)), and + optionally a behavior to be compared to.""" + #want to do queries wrt: behavior itself, the behavior packet, the querying behavior + if isinstance(predicateList, types.FunctionType): + predicateList = [predicateList] + elif not isinstance(prediateList, list): + raise Exception('Predicate list must be a function or list of functions') + global behaviorList, initialized + ret = [] + if not initialized: + initBQS() + + for behavior in behaviorList: #Consider every behavior + lastOutput = behavior.getLastOutput() + for output in lastOutput: #Look at every element it has output + validOutput = True + for pred in predicateList: #Evaluate every predicate. A predicate is a lambda function that + #takes a dict and returns a bool. + if not pred(output): + validOutput = False + break + if validOutput: + ret.append(output) + return ret + + diff --git a/util/ComponentRegistry.py b/util/ComponentRegistry.py index 776cd17..be913df 100644 --- a/util/ComponentRegistry.py +++ b/util/ComponentRegistry.py @@ -11,16 +11,19 @@ def clearRegistry(): initRegistry() def removeComponent(cid): - globals()['Registry'].pop(cid) + global Registry + Registry.pop(cid) def getComponent(cid): - return globals()['Registry'][cid] + global Registry + return Registry[cid] #Registry of all components of the light system #TODO: pick a graceful failure behavior and implement it def registerComponent(component, cid=None): + global Registry if cid != None: - globals()['Registry'][cid] = component + Registry[cid] = component else: try: cid = component['Id'] @@ -28,22 +31,26 @@ def registerComponent(component, cid=None): cid = getNewId() component['Id'] = cid main_log.debug(cid + 'automatically assigned') - globals()['Registry'][cid] = component + Registry[cid] = component return cid def verifyUniqueId(cid): - return not cid in globals()['Registry'] + global Registry + return not cid in Registry def removeComponent(cid): - globals()['Registry'].pop(cid) + global Registry + Registry.pop(cid) def getComponent(cid): - return globals()['Registry'][cid] + global Registry + return Registry[cid] def getNewId(): - trialKey = len(globals()['Registry']) + global Registry + trialKey = len(Registry) trialId = hashlib.md5(str(trialKey)).hexdigest() - while trialId in globals()['Registry']: + while trialId in Registry: trialKey += 1 trialId = hashlib.md5(str(trialKey)).hexdigest() return trialId diff --git a/util/Geo.py b/util/Geo.py index 43817ad..211e89d 100644 --- a/util/Geo.py +++ b/util/Geo.py @@ -2,8 +2,9 @@ import math from bisect import * import random -def pointWithinBoundingBox(point, bb): #this could be in 4 lines, but I'm lazy. - return sum([(point[i % 2] <= bb[i]) == (i>1) for i in range(4)]) == 4 +def pointWithinBoundingBox(point, bb): + """Returns whether or not a point (x,y) is within a bounding box (xmin, ymin, xmax, ymax)""" + return all([(point[i % 2] <= bb[i]) == (i>1) for i in range(4)]) def addLocations(l1,l2): return tuple([l1[i]+l2[i] for i in range(len(l1))]) |