From 4d62e1152241854ab864142d827d735d84405078 Mon Sep 17 00:00:00 2001 From: rcoh Date: Sat, 12 Feb 2011 15:59:59 -0500 Subject: 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. --- TestAll.py | 8 +- behaviors/ColorShift.py | 16 +++ behaviors/ModulateColor.py | 16 +++ config/C5Demo.xml | 231 ++++++++++++++++++++++++++++++++++++++ config/Demo.xml | 9 +- layouts/C5SignLayout.xml | 16 +++ layouts/ZigzagLayout.py | 11 +- operationscore/Behavior.py | 6 +- operationscore/SmootCoreObject.py | 2 + tests/TestBQS.py | 18 +++ tests/__init__.py | 3 + util/BehaviorQuerySystem.py | 39 +++++++ util/ComponentRegistry.py | 25 +++-- util/Geo.py | 5 +- 14 files changed, 377 insertions(+), 28 deletions(-) create mode 100644 behaviors/ColorShift.py create mode 100644 behaviors/ModulateColor.py create mode 100644 config/C5Demo.xml create mode 100644 layouts/C5SignLayout.xml create mode 100644 tests/TestBQS.py create mode 100644 util/BehaviorQuerySystem.py diff --git a/TestAll.py b/TestAll.py index 23b34ea..b24cf5e 100644 --- a/TestAll.py +++ b/TestAll.py @@ -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 @@ + + + + + simplemap + + + + layouts/C5SignLayout.xml + + + + pixelmappers.SimpleMapper + + simplemap + 20 + + + + pixelmappers.GaussianMapper + + gaussmap + 30 + 0.1 + 10 + 1 + + + + + + renderers/Pygame.xml + + + + + inputs.PygameInput + + pygameclick + 10 + True + + + + inputs.PygameInput + + pygamekey + 10 + True + + + + inputs.UDPInput + + udp + 3344 + 50 + + + + + inputs/MouseFollower.xml + + + + + behaviors/RandomColor.xml + + + (255,0,0) + (0,0,255) + + + + + behaviors/PixelDecay.xml + + + behaviors/SingleFrame.xml + + + behaviors/PixelDecay.xml + + .01 + + + + behaviors.XYMove + + xymove + 5 + 2 + + + + behaviors.RestrictLocation + + xbounce + {val}*-1 + XStep + {x}<0 or {x}>200 + + + + behaviors.RestrictLocation + + ybounce + {val}*-1 + YStep + {y}<0 or {y}>100 + + + + behaviors.BehaviorChain + + movebounce + + xymove + colorshift + ybounce + xbounce + + + + + behaviors.ModifyParam + + ysin + YStep + Sensor + 4*math.sin({x}/float(40)) + + + + behaviors.DebugBehavior + + debug + 0 + + pygamekey + udp + + + + + behaviors.Square + + square + 20 + + + + behaviors/LoopAndDie.xml + + 80 + + + + behaviors.BehaviorChain + + runcolordecay + + pygameclick + + + colorchange + mover + decay + + {'mover':'movebounce'} + True + gaussmap + + + + behaviors.ResponseMover + + mover + + + + behaviors.RandomWalk + + randmovement + 2 + + + + behaviors/Accelerate.xml + + + behaviors.EchoBehavior + + echo + 0 + False + + + + behaviors.ColorShift + + colorshift + + + + behaviors.BehaviorChain + + mousechaser + + followmouse + + + echo + square + singleframe + + True + + + + behaviors/RunningBehavior.xml + + + 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 @@ movebounce xymove + colorshift ybounce xbounce @@ -205,6 +206,12 @@ False + + behaviors.ColorShift + + colorshift + + behaviors.BehaviorChain @@ -217,7 +224,7 @@ square singleframe - True + False 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 @@ + + + layouts.ZigzagLayout + + strip1 + 10 + X + 1 + 20 + 20 + 50 + (0,0) + + + 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))]) -- cgit v1.2.3