From e34b4dbf8ba67c5374e43bea8b469172025a9163 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 29 Jan 2011 18:50:58 -0800 Subject: 6thFloorOsc commented out renderer to lights, default LightInstallation config and profile.py for OSC :) --- LightInstallation.py | 2 +- Profile.py | 2 +- config/6thFloorOSC.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LightInstallation.py b/LightInstallation.py index c1f01e8..41cc925 100755 --- a/LightInstallation.py +++ b/LightInstallation.py @@ -193,7 +193,7 @@ class LightInstallation(object): def main(argv): if len(argv) == 1: - l = LightInstallation('LightInstallationConfig.xml') + l = LightInstallation('config/6thFloor.xml') else: l = LightInstallation(argv[1]) diff --git a/Profile.py b/Profile.py index 2f180c9..2f796b2 100644 --- a/Profile.py +++ b/Profile.py @@ -1,4 +1,4 @@ import cProfile from LightInstallation import main -command = """main(['', 'config/6thFloor.xml'])""" +command = """main(['', 'config/6thFloorOSC.xml'])""" cProfile.runctx(command, globals(), locals(), filename="smootlight.profile") diff --git a/config/6thFloorOSC.xml b/config/6thFloorOSC.xml index cb1fd10..ca99b2b 100644 --- a/config/6thFloorOSC.xml +++ b/config/6thFloorOSC.xml @@ -29,9 +29,9 @@ - + renderers/Pygame.xml -- cgit v1.2.3 From 482a94fd48627153b923931d6ff21ebf57fad6f7 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 29 Jan 2011 19:34:24 -0800 Subject: removed duplicate OSC server--one is much faster. Building more into custom input would probably be even faster. --- config/6thFloorOSC.xml | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/config/6thFloorOSC.xml b/config/6thFloorOSC.xml index ca99b2b..05c2973 100644 --- a/config/6thFloorOSC.xml +++ b/config/6thFloorOSC.xml @@ -40,19 +40,19 @@ inputs.OSCInput - osc2 + osc 1234 10 - + inputs.PygameInput @@ -110,7 +110,7 @@ 3 - + sixaxis @@ -188,7 +188,7 @@ {y}<0 or {y}>200 - + + behaviors.AllPixelsLeft @@ -245,7 +245,7 @@ allpixels - + + behaviors.BehaviorChain - mousechaser2 + SixaxisChase - osc2 + osc sixaxis @@ -339,9 +339,8 @@ behaviors.BehaviorChain - mousechaser + OSCTouchChase - osc -- cgit v1.2.3 From a5d899c90ae4b15e24656e83adccc98e02b57469 Mon Sep 17 00:00:00 2001 From: rcoh Date: Tue, 1 Feb 2011 23:04:47 -0500 Subject: Profile changes. --- Profile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Profile.py b/Profile.py index daabb6b..a1eb581 100644 --- a/Profile.py +++ b/Profile.py @@ -1,4 +1,4 @@ import cProfile from LightInstallation import main -command = """main(['', 'config/Kuan.xml'])""" +command = """main(['', 'config/Demo.xml'])""" cProfile.runctx(command, globals(), locals(), filename="smootlight.profile") -- cgit v1.2.3 From 9b134eb47a93c2317519c07dc5a3c3522c9fa2f4 Mon Sep 17 00:00:00 2001 From: rcoh Date: Tue, 1 Feb 2011 23:05:06 -0500 Subject: Cleanup demo code --- config/Demo.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/Demo.xml b/config/Demo.xml index 525e468..4e811ba 100644 --- a/config/Demo.xml +++ b/config/Demo.xml @@ -68,7 +68,7 @@ 10 --> - + inputs/MouseFollower.xml @@ -152,7 +152,7 @@ - behaviors.Square + behaviors.AllPixels square 20 -- cgit v1.2.3 From 082e4b0c53123dd377da148541f7d98516716862 Mon Sep 17 00:00:00 2001 From: rcoh Date: Tue, 1 Feb 2011 23:13:57 -0500 Subject: cleanup on aisle 3 (in 6thfloorosc.xml) --- config/6thFloorOSC.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/6thFloorOSC.xml b/config/6thFloorOSC.xml index 792fd0c..ce60c74 100644 --- a/config/6thFloorOSC.xml +++ b/config/6thFloorOSC.xml @@ -273,9 +273,9 @@ touchosc - square - singleframe + decay + gaussmap True -- cgit v1.2.3 From 834cd2b0eaa27cfdba67d712d5d14120c6a441c3 Mon Sep 17 00:00:00 2001 From: rcoh Date: Fri, 4 Feb 2011 23:08:22 -0500 Subject: couple changes --- pixelcore/Pixel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pixelcore/Pixel.py b/pixelcore/Pixel.py index 4e7cfce..cf5a328 100644 --- a/pixelcore/Pixel.py +++ b/pixelcore/Pixel.py @@ -8,7 +8,7 @@ class Pixel: 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.""" + weighted by z-index. To get the current color of the pixel, call the state method.""" radius = 2 timeOff = -1 -- cgit v1.2.3 From 06c639db6b98affab4abf07e57a90e2fcb5402ef Mon Sep 17 00:00:00 2001 From: rcoh Date: Sat, 5 Feb 2011 22:34:34 -0500 Subject: Early stages of param-binding in xml. Functional. RCOH --- operationscore/SmootCoreObject.py | 11 ++++++++--- tests/TestConfigLoaders.py | 9 ++++++++- util/ColorOps.py | 4 ++++ util/Config.py | 10 ++++++++++ util/Geo.py | 7 +++++++ 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/operationscore/SmootCoreObject.py b/operationscore/SmootCoreObject.py index 0d32773..51b84e3 100644 --- a/operationscore/SmootCoreObject.py +++ b/operationscore/SmootCoreObject.py @@ -2,6 +2,7 @@ import pdb import threading import thread import util.Config as configGetter +import types class SmootCoreObject(object): """SmootCoreObject is essentially a super-object class which grants us some niceties. It allows @@ -34,9 +35,13 @@ class SmootCoreObject(object): def __setitem__(self,k, item): self.argDict[k] = item - def __getitem__(self, item): - if item in self.argDict: - return self.argDict[item] + def __getitem__(self, key): + if key in self.argDict: + item = self.argDict[key] + if isinstance(item, types.FunctionType): + return item(self.argDict) #resolve the lambda function, if it exists + else: + return item else: return None def __contains__(self, item): diff --git a/tests/TestConfigLoaders.py b/tests/TestConfigLoaders.py index c79bbf1..c7e2b7a 100644 --- a/tests/TestConfigLoaders.py +++ b/tests/TestConfigLoaders.py @@ -29,6 +29,13 @@ class TestConfigLoaders(unittest.TestCase): result.write('tests/testdata/inheritanceTESTout.xml') assert filecmp.cmp('tests/testdata/inheritanceTESTout.xml',\ 'tests/testdata/inheritanceTRUTH.xml') - + #Tests our fancy new XML Eval Function + def test_eval(self): + assert Config.attemptEval('5') == 5 + assert Config.attemptEval('{5:10, 12:15}') == {5:10, 12:15} + singleLayerLambda = Config.attemptEval('${Val}$*5') + assert singleLayerLambda({'Val':2}) == 10 + doubleLayerLambda = Config.attemptEval("${Val1}$*'${Val2}$'") + assert doubleLayerLambda({'Val1':3})({'Val2':7}) == 21 if __name__ == '__main__': unittest.main() diff --git a/util/ColorOps.py b/util/ColorOps.py index 4b1162a..796a902 100644 --- a/util/ColorOps.py +++ b/util/ColorOps.py @@ -40,3 +40,7 @@ def randomBrightColor(): hue, sat, val = colorsys.hsv_to_rgb(hue, sat, val) ret = [hue, sat, val] return floatToIntColor(ret) + +class Color(object): + def __init__(self, r,g,b): + self.rep = [r,g,b] diff --git a/util/Config.py b/util/Config.py index 6fdb0d4..4153313 100644 --- a/util/Config.py +++ b/util/Config.py @@ -1,4 +1,5 @@ from xml.etree.ElementTree import * +import re import sys import xml import pdb @@ -113,6 +114,15 @@ def pullArgsFromItem(parentNode): def attemptEval(val): try: + if '${' in val and '}$' in val: #TODO: this could be a little cleaner + dictVal = re.sub("'\$\{(.+)\}\$'", "b['\\1']", val) #replace all expressions like {blah} with a['blah'] + dictVal = re.sub("\$\{(.+)\}\$", "a['\\1']", dictVal) #replace all expressions like {blah} with a['blah'] + if "'${" and "}$'" in val: #nested lambda madness + lambdaVal = eval('lambda a: lambda b: ' + dictVal) + else: + lambdaVal = eval('lambda a:'+dictVal) #TODO: nested lambdas + return lambdaVal #convert referential objects to lambda expressions which resolve + #dynamically val = eval(val) except (NameError, SyntaxError): val = str(val) diff --git a/util/Geo.py b/util/Geo.py index 0dde80b..43817ad 100644 --- a/util/Geo.py +++ b/util/Geo.py @@ -32,3 +32,10 @@ def windtrail(x,y,height,center,width): b=center c=width return a*((math.exp(-((x-b))/(c)))**2)*(math.exp(-((y))/(0.2*c)))**2 + +class Location(object): + def __init__(self,x=0,y=0): + self.x = x + self.y = y + def __add__(self, b): + return Location(self.x+b.x, self.y+b.y) -- cgit v1.2.3 From b45b9079c5decd720d8275378bb0d6dc172c6234 Mon Sep 17 00:00:00 2001 From: rcoh Date: Wed, 9 Feb 2011 12:14:27 -0500 Subject: Early stages of support for interbehavior interactions. Fixed a bug in the config eval and added some new tests. --- Profile.py | 2 +- operationscore/Behavior.py | 8 +++++--- operationscore/Input.py | 2 +- tests/TestConfigLoaders.py | 7 +++++++ util/Config.py | 13 ++++++++----- 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Profile.py b/Profile.py index a1eb581..40c2d0a 100644 --- a/Profile.py +++ b/Profile.py @@ -1,4 +1,4 @@ import cProfile from LightInstallation import main -command = """main(['', 'config/Demo.xml'])""" +command = """main(['', 'config/FireflyDemo.xml'])""" cProfile.runctx(command, globals(), locals(), filename="smootlight.profile") diff --git a/operationscore/Behavior.py b/operationscore/Behavior.py index 6424842..97fa020 100644 --- a/operationscore/Behavior.py +++ b/operationscore/Behavior.py @@ -1,9 +1,6 @@ - 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): """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 @@ -44,6 +41,11 @@ class Behavior(SmootCoreObject): else: self.addInput(sensorInputs) #private + def getLastOutput(self): + return self.lastState + def setLastOutput(self, output): + """Override to modify state.""" + self.lastState = output def addMapperToResponse(self, responses): if self['Mapper'] != None: if type(responses) == type(tuple): diff --git a/operationscore/Input.py b/operationscore/Input.py index d3d5644..5a835ec 100644 --- a/operationscore/Input.py +++ b/operationscore/Input.py @@ -17,7 +17,7 @@ class Input(ThreadedSmootCoreObject): self.inputInit() def respond(self, eventDict): - #if eventDict != []: + eventDict['InputId'] = self['Id'] self.parentScope.lock.acquire() self.parentScope.processResponse(self.argDict, eventDict) self.parentScope.lock.release() diff --git a/tests/TestConfigLoaders.py b/tests/TestConfigLoaders.py index c7e2b7a..02c8865 100644 --- a/tests/TestConfigLoaders.py +++ b/tests/TestConfigLoaders.py @@ -37,5 +37,12 @@ class TestConfigLoaders(unittest.TestCase): assert singleLayerLambda({'Val':2}) == 10 doubleLayerLambda = Config.attemptEval("${Val1}$*'${Val2}$'") assert doubleLayerLambda({'Val1':3})({'Val2':7}) == 21 + + conditional = Config.attemptEval("${Val1}$*5=='${Val2}$'") + assert conditional({'Val1':5})({'Val2':25}) == True + assert conditional({'Val1':5})({'Val2':26}) == False + + onlyDouble = Config.attemptEval("'${Val1}$'*'${Val2}$'") + assert onlyDouble({})({'Val1':3, 'Val2':7}) == 21 if __name__ == '__main__': unittest.main() diff --git a/util/Config.py b/util/Config.py index 4153313..25018a8 100644 --- a/util/Config.py +++ b/util/Config.py @@ -113,17 +113,20 @@ def pullArgsFromItem(parentNode): return args def attemptEval(val): + """Runs an eval if possible, or converts into a lambda expression if indicated. Otherwise, + leaves as a string.""" try: if '${' in val and '}$' in val: #TODO: this could be a little cleaner - dictVal = re.sub("'\$\{(.+)\}\$'", "b['\\1']", val) #replace all expressions like {blah} with a['blah'] - dictVal = re.sub("\$\{(.+)\}\$", "a['\\1']", dictVal) #replace all expressions like {blah} with a['blah'] + dictVal = re.sub("'\$\{(.+?)\}\$'", "b['\\1']", val) #replace expressions '${blah}$' with b['blah'] + dictVal = re.sub("\$\{(.+?)\}\$", "a['\\1']", dictVal) #replace all expressions like {blah} with a['blah'] if "'${" and "}$'" in val: #nested lambda madness lambdaVal = eval('lambda a: lambda b: ' + dictVal) else: lambdaVal = eval('lambda a:'+dictVal) #TODO: nested lambdas - return lambdaVal #convert referential objects to lambda expressions which resolve - #dynamically - val = eval(val) + return lambdaVal #convert referential objects to lambda expressions which can be + #resolved dynamically. + else: + val = eval(val) except (NameError, SyntaxError): val = str(val) return val -- cgit v1.2.3 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 From cf1048df72b845ef7fefd5ec5709f7d1b2c4df79 Mon Sep 17 00:00:00 2001 From: rcoh Date: Sat, 12 Feb 2011 16:34:25 -0500 Subject: Added some new tests to BehaviorQuerySystem.py which demonstrate how to use it. Fixed a bug in BQS. --- tests/TestBQS.py | 30 ++++++++++++++++++++++++++---- util/BehaviorQuerySystem.py | 2 +- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/tests/TestBQS.py b/tests/TestBQS.py index 8dc90b2..7316c31 100644 --- a/tests/TestBQS.py +++ b/tests/TestBQS.py @@ -1,18 +1,40 @@ import unittest import util.BehaviorQuerySystem as bqs from behaviors.ColorChangerBehavior import * +import util.Geo as geo class TestBQS(unittest.TestCase): def setUp(self): bqs.initBQS() b = ColorChangerBehavior({'Id': 'color','ColorList':[(255,0,0)]}) + c = ColorChangerBehavior({'Id': 'color2', 'ColorList':[(0,0,255)]}) bqs.addBehavior(b) - b.addInput({'Location':(5,5)}) + bqs.addBehavior(c) + b.addInput({'Location':(3,4)}) + c.addInput({'Location':(5,12)}) b.timeStep() + c.timeStep() def tearDown(self): bqs.initBQS() - - def test_color_predicate(self): + def test_simple_query(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(validQuery) == [{'Color':(255,0,0), 'Location':(3,4)}] assert bqs.query(invalidQuery) == [] + def test_dist_query(self): + validDist = lambda args:geo.dist(args['Location'], (0,0)) <= 5 + invalidDist = lambda args:geo.dist(args['Location'], (0,0)) <= 2 + doubleDist = lambda args:geo.dist(args['Location'], (0,0)) <= 20 + + assert bqs.query(validDist) == [{'Color':(255,0,0), 'Location':(3,4)}] + assert bqs.query(invalidDist) == [] + assert bqs.query(doubleDist) == [{'Color':(255,0,0), 'Location':(3,4)}, {'Color':(0,0,255),\ + 'Location':(5,12)}] + def test_complex_queries(self): + + validQuery = lambda args:args['Color']==(255,0,0) + doubleDist = lambda args:geo.dist(args['Location'], (0,0)) <= 20 + + twoPartPredicate = lambda args:doubleDist(args) and validQuery(args) + assert bqs.query(twoPartPredicate) == [{'Color':(255,0,0), 'Location':(3,4)}] + assert bqs.query([validQuery, doubleDist]) == [{'Color':(255,0,0), 'Location':(3,4)}] + diff --git a/util/BehaviorQuerySystem.py b/util/BehaviorQuerySystem.py index 5399435..643b95c 100644 --- a/util/BehaviorQuerySystem.py +++ b/util/BehaviorQuerySystem.py @@ -16,7 +16,7 @@ def query(predicateList): #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): + elif not isinstance(predicateList, list): raise Exception('Predicate list must be a function or list of functions') global behaviorList, initialized ret = [] -- cgit v1.2.3 From a9d8716e974611f17f2a66b66d905cb81cffa5fc Mon Sep 17 00:00:00 2001 From: rcoh Date: Sat, 12 Feb 2011 16:42:59 -0500 Subject: Add new behaviors to the BQS --- LightInstallation.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/LightInstallation.py b/LightInstallation.py index c1f01e8..aba5ae1 100755 --- a/LightInstallation.py +++ b/LightInstallation.py @@ -7,6 +7,7 @@ import pdb, sys, time, thread import util.TimeOps as clock import util.Config as configGetter import util.ComponentRegistry as compReg +import util.BehaviorQuerySystem as bqs from logger import main_log #Python class to instantiate and drive a Screen through different patterns, #and effects. @@ -27,7 +28,8 @@ class LightInstallation(object): self.screen = Screen() compReg.initRegistry() compReg.registerComponent(self.screen, 'Screen') #TODO: move to constants file - + + bqs.initBQS() #initialize the behavior query system #read configs from xml config = configGetter.loadConfigFile(configFileName) @@ -171,6 +173,7 @@ class LightInstallation(object): self.behaviors = self.initializeComponent(behaviorConfig) for behavior in self.behaviors: self.addBehavior(behavior) + bqs.addBehavior(behavior) def addBehavior(self, behavior): """Does work needed to add a behavior: currently -- maps behavior inputs into the input behavior -- cgit v1.2.3