aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar rcoh <rcoh@mit.edu>2011-02-12 15:59:59 -0500
committerGravatar rcoh <rcoh@mit.edu>2011-02-12 15:59:59 -0500
commit4d62e1152241854ab864142d827d735d84405078 (patch)
tree2589c6a37721e1b8362c8e8c6a580c8c185b2f86
parentb45b9079c5decd720d8275378bb0d6dc172c6234 (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.py8
-rw-r--r--behaviors/ColorShift.py16
-rw-r--r--behaviors/ModulateColor.py16
-rw-r--r--config/C5Demo.xml231
-rw-r--r--config/Demo.xml9
-rw-r--r--layouts/C5SignLayout.xml16
-rw-r--r--layouts/ZigzagLayout.py11
-rw-r--r--operationscore/Behavior.py6
-rw-r--r--operationscore/SmootCoreObject.py2
-rw-r--r--tests/TestBQS.py18
-rw-r--r--tests/__init__.py3
-rw-r--r--util/BehaviorQuerySystem.py39
-rw-r--r--util/ComponentRegistry.py25
-rw-r--r--util/Geo.py5
14 files changed, 377 insertions, 28 deletions
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 @@
+<!---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}&lt;0 or {x}&gt;200</LocationRestriction>
+ </Args>
+ </Behavior>
+ <Behavior>
+ <Class>behaviors.RestrictLocation</Class>
+ <Args>
+ <Id>ybounce</Id>
+ <Action>{val}*-1</Action>
+ <ParamName>YStep</ParamName>
+ <LocationRestriction>{y}&lt;0 or {y}&gt;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))])