diff options
39 files changed, 402 insertions, 341 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d4e9cb --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.pyc +*.swp +*.swo +*.pyo diff --git a/LightInstallation.py b/LightInstallation.py index 87aa629..5882806 100644 --- a/LightInstallation.py +++ b/LightInstallation.py @@ -1,8 +1,11 @@ from xml.etree.ElementTree import ElementTree from pixelcore.Screen import * from pixelcore.PixelStrip import * -import pdb, sys, time, Util, thread +import pdb, sys, time, thread from pygame.locals import * +import util.TimeOps as clock +import util.Config as configGetter +import util.ComponentRegistry as compReg from logger import main_log #Python class to instantiate and drive a Screen through different patterns, #and effects. @@ -10,7 +13,7 @@ class LightInstallation: def __init__(self, configFileName): main_log.critical("hi russell, i'm sending info to the log files") main_log.critical("initializing based on file: " + str(configFileName)) - self.timer = Util.Stopwatch() + self.timer = clock.Stopwatch() self.timer.start() self.inputs = {} #dict of inputs and their bound behaviors, keyed by InputId self.behaviors = {} @@ -20,38 +23,43 @@ class LightInstallation: self.componentDict = {} self.inputBehaviorRegistry = {} #inputid -> behaviors listening to that #input - #give Util a pointer to our componentRegistry and screen so that everyone can use - #it - Util.setComponentDict(self.componentDict) self.screen = Screen() - Util.setScreen(self.screen) - config = Util.loadConfigFile(configFileName) + compReg.initRegistry() + compReg.registerComponent(self.screen, 'Screen') #TODO: move to constants file + config = configGetter.loadConfigFile(configFileName) #read configs from xml rendererConfig = config.find('RendererConfiguration') pixelConfig = config.find('PixelConfiguration') inputConfig = config.find('InputConfiguration') behaviorConfig = config.find('BehaviorConfiguration') mapperConfig = config.find('PixelMapperConfiguration') + + installationConfig = config.find('InstallationConfiguration') #inits self.initializeScreen(pixelConfig) self.initializeRenderers(rendererConfig) self.initializeInputs(inputConfig) self.initializeBehaviors(behaviorConfig) self.initializeMapper(mapperConfig) - - self.screen.setMapper(self.mapper) #registration in dict self.registerComponents(self.renderers) self.registerComponents(self.inputs) self.registerComponents(self.behaviors) + self.registerComponents(self.mappers) + self.configureInstallation(installationConfig) #Done initializing. Lets start this thing! self.timer.stop() print 'Initialization done. Time: ', self.timer.elapsed(), 'ms' self.mainLoop() + def configureInstallation(self, installationConfig): + defaults = configGetter.generateArgDict(installationConfig.find('Defaults')) + for defaultSelection in defaults: + componentToMap = compReg.getComponent(defaults[defaultSelection]) + compReg.registerComponent(compReg.getComponent(defaults[defaultSelection]),\ + 'Default'+defaultSelection) def initializeMapper(self, mapperConfig): - self.mapper = self.initializeComponent(mapperConfig)[0] #TODO: support - #multiple mappers + self.mappers = self.initializeComponent(mapperConfig) def initializeScreen(self, layoutConfig): pixelAssemblers = self.initializeComponent(layoutConfig) [self.addPixelStrip(l) for l in pixelAssemblers] @@ -72,14 +80,14 @@ class LightInstallation: cid = component['Id'] if cid == None: raise Exception('Components must have Ids!') - self.componentDict[cid] = component + compReg.registerComponent(component) def initializeComponent(self, config): components = [] if config != None: for configItem in config.getchildren(): [module,className] = configItem.find('Class').text.split('.') exec('from ' + module+'.'+className + ' import *') - args = Util.generateArgDict(configItem.find('Args')) + args = configGetter.generateArgDict(configItem.find('Args')) args['parentScope'] = self #TODO: we shouldn't give away scope #like this, find another way. components.append(eval(className+'(args)')) #TODO: doesn't error @@ -89,12 +97,12 @@ class LightInstallation: return True def mainLoop(self): #self.screen.allOn() - lastLoopTime = Util.time() + lastLoopTime = clock.time() refreshInterval = 30 runCount = 10000 while runCount > 0: runCount -= 1 - loopStart = Util.time() + loopStart = clock.time() responses = self.evaluateBehaviors() #inputs are all queued when they #happen, so we only need to run the behaviors self.timer.start() @@ -102,7 +110,7 @@ class LightInstallation: response != []] self.screen.timeStep() [r.render(self.screen) for r in self.renderers] - loopElapsed = Util.time()-loopStart + loopElapsed = clock.time()-loopStart sleepTime = max(0,refreshInterval-loopElapsed) self.timer.stop() #print self.timer.elapsed() @@ -124,9 +132,6 @@ class LightInstallation: self.behaviors = self.initializeComponent(behaviorConfig) for behavior in self.behaviors: self.addBehavior(behavior) - #TODO: we probably don't need this anymore :( - def topologicalBehaviorSort(self): - return Util.topologicalSort(self.behaviorDependencies) #Does work needed to add a behavior: currently -- maps behavior inputs into #the input behavior registry. def addBehavior(self, behavior): @@ -136,8 +141,12 @@ class LightInstallation: def processResponse(self,inputDict, responseDict): inputId = inputDict['Id'] boundBehaviorIds = self.inputBehaviorRegistry[inputId] - [self.componentDict[b].addInput(responseDict) for b in boundBehaviorIds] - + #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!' def main(argv): print argv if len(argv) == 1: diff --git a/Util.py b/Util.py deleted file mode 100644 index 07ce545..0000000 --- a/Util.py +++ /dev/null @@ -1,232 +0,0 @@ -import pdb -from xml.etree.ElementTree import ElementTree -import math,struct -from bisect import * -#import json # json.loads() to decode string; json.dumps() to encode data -import socket -import random -from pygame.locals import * -import time as clock -from pixelevents.StepEvent import * - -VERSION = 0x0001 -MAGIC = 0x4adc0104 -MOREMAGIC = 0xdeadbeef -DEEPMAGIC = 0xc001d00d -MAGICHASH = 0x69000420 -PORTOUT = 0x0108 -classArgsMem = {} -UNI = 0 -colorByteMem = {} -CONFIG_PATH = 'config/' -kinetDict = {'flags': 0, 'startcode': 0, 'pad':0} -componentDict = {} -#Only for rough estimates. Kindof lazy on specifics. -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 -print pointWithinBoundingBox((118,21), (10,8,298,42)) -def addLocations(l1,l2): - return tuple([l1[i]+l2[i] for i in range(len(l1))]) -def setScreen(screen): - globals()["screen"] = screen -def getScreen(): - return screen -def setComponentDict(componentDictRef): - globals()["componentDict"] = componentDictRef -def getComponentById(cid): - if cid in componentDict: - return componentDict[cid] - else: - return None -def addPixelEventIfMissing(responseDict): - if not 'PixelEvent' in responseDict: - if 'Color' in responseDict: - color = responseDict['Color'] - else: - raise Exception('Need Color. Probably') - responseDict['PixelEvent'] = StepEvent.generate(300, color) -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(sum([(l1[i]-l2[i])**2 for i in range(len(l1))])) -def time(): - return clock.time()*1000 -def randomColor(): - return [random.randint(0,255) for i in range(3)] -def chooseRandomColor(colorList): - return random.choice(colorList) -def loadParamRequirementDict(className): - if not className in classArgsMem: #WOO CACHING - classArgsMem[className] = fileToDict(CONFIG_PATH + className) - return classArgsMem[className] -def loadConfigFile(fileName): - try: - fileName = CONFIG_PATH + fileName - if '.params' in fileName: - return fileToDict(fileName) - if '.xml' in fileName: - config = ElementTree() - config.parse(fileName) - return config - except: - return None -def fileToDict(fileName): - fileText = '' - try: - print 'File Read' - with open(fileName) as f: - for line in f: - fileText += line.rstrip('\n').lstrip('\t') + ' ' - except IOError: - return {} - if fileText == '': - return {} - return eval(fileText) -def find_le(a, 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' - return bisect_left(a, x) -def safeColor(c): - return [min(channel,255) for channel in c] -def combineColors(c1,c2): - return safeColor([c1[i]+c2[i] for i in range(min(len(c1),len(c2)))]) -def multiplyColor(color, percent): - return safeColor([channel*(percent) for channel in color]) -#parses arguments into python objects if possible, otherwise leaves as strings -def generateArgDict(parentNode, recurse=False): - args = {} - for arg in parentNode.getchildren(): - key = arg.tag - if arg.getchildren() != []: - 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) - if key in args: #build of lists of like-elements - if type(args[key]) != type([]): - args[key] = [args[key]] - args[key].append(value) - else: - args[key]=value - #if we should be a list but we aren't: - if len(args.keys()) == 1 and recurse: - return args[args.keys()[0]] - return args -#Given a dictionary of connections, returns their topological ordering -- (the -#order in which they can be visited such that all parents have been visited -#before their children. Returns the order or None if no such ordering exists -#(the graph contains a cycle). -def topologicalSort(adjacencyDict): - def dfsVisit(vertex): - gray[vertex] = 1 - for child in adjacencyDict[vertex]: - if not child in visited: - if child in gray: #We have a cycle. No topological ordering - #exists! - raise Exception('Cycle!') - dfsVisit(child) - orderedList.insert(0, vertex) - visited[vertex] = 1 - orderedList = [] - visited = {} - gray = {} - for vertex in adjacencyDict: - try: - if not vertex in visited: - dfsVisit(vertex) - except: - return None #cycle - return orderedList -def topoTest(): - adj = {'a':['d','c'], 'b':['c'], 'c':['e'], 'd':['e'], 'e':[]} - print topologicalOrdering(adj) -def getConnectedSocket(ip,port): - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - print (ip, port) - sock.connect((ip, port)) - return sock -def composePixelStripData(pixelStrip): - packet = bytearray() - for light in pixelStrip: - color = light.state() - for channel in color: #skip the last value, its an - #alpha value - packet.append(struct.pack('B', channel)) - return packet -# packet = [0]*len(pixelStrip.pixels)*3 #preallocate for speed -# for i in range(len(pixelStrip.pixels)): -#color = pixelStrip.pixels[i].state() -#packet[i:i+2] = color -# return bytearray(packet) -def composePixelStripPacket(pixelStrip,port): - packet = bytearray() - data = composePixelStripData(pixelStrip) - subDict = dict(kinetDict) - subDict['len'] = 38000 #I have no idea why this works. - subDict['port'] = port - #pdb.set_trace() - packet.extend(kinetPortOutPacket(subDict)) - packet.append(0x0) - packet.extend(data) - return packet -def kinetHeader(): - header = bytearray() - header.extend(struct.pack('L', MAGIC)) - header.extend(struct.pack('H', VERSION)) - header.extend(struct.pack('H', PORTOUT)) - header.extend(struct.pack('L', 0)) - return header -def kinetPortOut(): - header = kinetHeader() - header.extend(struct.pack('L', UNI)) - return header -def kinetPortOutPayload(argDict): - payload = bytearray() - payload.extend(struct.pack('B', argDict['port'])) - #payload.append(0x00) #somepadding? lolwtf. - 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'])) - #pdb.set_trace() - return payload -def kinetPortOutPacket(payloadArgs): - packet = bytearray() - packet.extend(kinetPortOut()) - packet.extend(kinetPortOutPayload(payloadArgs)) - return packet -def testXMLParse(fileName): - #pdb.set_trace() - config = ElementTree() - config.parse(fileName) - print generateArgDict(config.find('ChildElement')) - print generateArgDict(config.find('Renderer')) -class Stopwatch: - def __init__(self): - self.running = False - self.startTime = -1 - self.stopTime = -1 - def start(self): - self.startTime = Util.time() - self.running = True - def elapsed(self): - if self.running: - return Util.time()-self.startTime - else: - return self.stopTime - self.startTime - def stop(self): - self.stopTime = Util.time() - self.running = False -##CONSTANTS## -location = 'Location' - - diff --git a/behaviors/AllPixelsLeft.py b/behaviors/AllPixelsLeft.py new file mode 100644 index 0000000..7f731e9 --- /dev/null +++ b/behaviors/AllPixelsLeft.py @@ -0,0 +1,11 @@ +from operationscore.Behavior import * +import util.ComponentRegistry as compReg +import pdb +class AllPixelsLeft(Behavior): + def processResponse(self, sensorInputs, recursiveInputs): + for sensory in sensorInputs: + xLoc = sensory['Location'][0] + if type(xLoc) == type(tuple()): + pdb.set_trace() + sensory['Location'] = '[{x}<' + str(xLoc) + ']' + return (sensorInputs, recursiveInputs) diff --git a/behaviors/BehaviorChain.py b/behaviors/BehaviorChain.py index fe50573..98585c9 100644 --- a/behaviors/BehaviorChain.py +++ b/behaviors/BehaviorChain.py @@ -1,5 +1,5 @@ from operationscore.Behavior import * -import Util +import util.ComponentRegistry as compReg import pdb class BehaviorChain(Behavior): def behaviorInit(self): @@ -10,7 +10,7 @@ class BehaviorChain(Behavior): def processResponse(self, sensorInputs, recursiveInputs): response = sensorInputs for behaviorId in self['ChainedBehaviors']: - behavior = Util.getComponentById(behaviorId) + behavior = compReg.getComponent(behaviorId) if behaviorId in self.feedback: recurrence = self.feedback[behaviorId] else: @@ -19,7 +19,7 @@ class BehaviorChain(Behavior): recurrence) if behaviorId in self.hooks: #process recursive hook if there is one - hookBehavior = Util.getComponentById(self.hooks[behaviorId]) + hookBehavior = compReg.getComponent(self.hooks[behaviorId]) #we feed its recurrence in as input to the behavior. (recurrence, hookRecurrence) = \ hookBehavior.immediateProcessInput(recurrence, \ diff --git a/behaviors/ColorChangerBehavior.py b/behaviors/ColorChangerBehavior.py index a3b1739..e1827eb 100644 --- a/behaviors/ColorChangerBehavior.py +++ b/behaviors/ColorChangerBehavior.py @@ -1,5 +1,5 @@ from operationscore.Behavior import * -import Util +import util.ColorOps as color import pdb class ColorChangerBehavior(Behavior): def processResponse(self, sensorInputs, recursiveInputs): @@ -7,9 +7,8 @@ class ColorChangerBehavior(Behavior): for sensory in sensorInputs: newDict = dict(sensory) #don't run into shallow copy issues if self['ColorList'] != None: - newDict['Color'] = Util.chooseRandomColor(self['ColorList']) #TODO: this doesn't work. + newDict['Color'] = color.chooseRandomColor(self['ColorList']) #TODO: this doesn't work. else: - newDict['Color'] = Util.randomColor() -#newDict['Color'] = (255,0,0) + newDict['Color'] = color.randomColor() ret.append(newDict) return (ret, recursiveInputs) diff --git a/behaviors/DebugBehavior.py b/behaviors/DebugBehavior.py index 4c8550a..a00346b 100644 --- a/behaviors/DebugBehavior.py +++ b/behaviors/DebugBehavior.py @@ -1,5 +1,4 @@ from operationscore.Behavior import * -import Util import pdb class DebugBehavior(Behavior): def processResponse(self, sensorInputs, recursiveInputs): diff --git a/behaviors/DecayBehavior.py b/behaviors/DecayBehavior.py index edc833d..c1f6f92 100644 --- a/behaviors/DecayBehavior.py +++ b/behaviors/DecayBehavior.py @@ -1,13 +1,13 @@ from operationscore.Behavior import * from pixelevents.DecayEvent import * -import Util +import util.Strings as Strings import pdb class DecayBehavior(Behavior): def processResponse(self, sensorInputs, recursiveInputs): ret = [] for sensory in sensorInputs: outDict = {} - outDict[Util.location] = sensory[Util.location] + outDict[Strings.LOCATION] = sensory[Strings.LOCATION] outDict['PixelEvent'] = \ DecayEvent.generate(self['DecayType'],self['Coefficient'], sensory['Color']) ret.append(outDict) diff --git a/behaviors/EchoBehavior.py b/behaviors/EchoBehavior.py index a11558a..be0ed14 100644 --- a/behaviors/EchoBehavior.py +++ b/behaviors/EchoBehavior.py @@ -1,12 +1,12 @@ from operationscore.Behavior import * -import Util +import util.Strings as Strings import pdb class EchoBehavior(Behavior): def processResponse(self, sensorInputs, recursiveInputs): ret = [] for sensory in sensorInputs: outDict = {} - outDict[Util.location] = sensory[Util.location] + outDict[Strings.LOCATION] = sensory[Strings.LOCATION] outDict['Color'] = (255,0,0) ret.append(outDict) return ret diff --git a/behaviors/ModifyParam.py b/behaviors/ModifyParam.py index 38b8cd5..3701013 100644 --- a/behaviors/ModifyParam.py +++ b/behaviors/ModifyParam.py @@ -1,5 +1,4 @@ from operationscore.Behavior import * -import Util 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): diff --git a/behaviors/RunningBehavior.py b/behaviors/RunningBehavior.py index 3d82d29..92db69b 100644 --- a/behaviors/RunningBehavior.py +++ b/behaviors/RunningBehavior.py @@ -1,6 +1,7 @@ from operationscore.Behavior import * +import util.ComponentRegistry as compReg +import util.Geo as Geo import pdb -import Util class RunningBehavior(Behavior): def processResponse(self, sensorInputs, recursiveInputs): newResponses = sensorInputs @@ -12,10 +13,10 @@ class RunningBehavior(Behavior): outDict['Dir'] = 1 #to the right if not 'StepSize' in outDict: outDict['StepSize'] = self['StepSize'] - outDict['Location']= Util.addLocations(outDict['Location'], + outDict['Location']= Geo.addLocations(outDict['Location'], (outDict['StepSize']*outDict['Dir'],0)) - if not Util.pointWithinBoundingBox(outDict['Location'], \ - Util.getScreen().getSize()): + if not Geo.pointWithinBoundingBox(outDict['Location'], \ + compReg.getComponent('Screen').getSize()): outDict['Dir'] *= -1 ret.append(outDict) ret += newResponses diff --git a/config/Outdoor.xml b/config/Outdoor.xml index 7f054f0..1cf00e4 100644 --- a/config/Outdoor.xml +++ b/config/Outdoor.xml @@ -1,5 +1,10 @@ <!---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> <PixelStrip> <Class>layouts.LineLayout</Class> @@ -62,15 +67,13 @@ </Args> </PixelStrip> </PixelConfiguration> - <!--<PixelMapperConfiguration> + <PixelMapperConfiguration> <PixelMapper> <Class>pixelmappers.SimpleMapper</Class> <Args> <Id>simplemap</Id> </Args> </PixelMapper> - </PixelMapperConfiguration>--> - <PixelMapperConfiguration> <PixelMapper> <Class>pixelmappers.GaussianMapper</Class> <Args> @@ -120,7 +123,7 @@ <Class>inputs.PygameInput</Class> <Args> <Id>followmouse</Id> - <RefreshInterval>10</RefreshInterval> + <RefreshInterval>50</RefreshInterval> <FollowMouse>True</FollowMouse> </Args> </InputElement> @@ -181,6 +184,12 @@ </Args> </Behavior> <Behavior> + <Class>behaviors.AllPixelsLeft</Class> + <Args> + <Id>pixelsleft</Id> + </Args> + </Behavior> + <Behavior> <Class>behaviors.RecursiveDecay</Class> <Args> <Id>recursivedecay</Id> @@ -201,6 +210,7 @@ </ChainedBehaviors> <RecursiveHooks>{'running':'acceleratedie'}</RecursiveHooks> <RenderToScreen>True</RenderToScreen> + <Mapper>gaussmap</Mapper> </Args> </Behavior> <Behavior> @@ -231,6 +241,7 @@ </Inputs> <ChainedBehaviors> <Id>echo</Id> + <Id>pixelsleft</Id> <Id>colorchange</Id> <Id>decay</Id> </ChainedBehaviors> diff --git a/inputs/PygameInput.py b/inputs/PygameInput.py index 1f438d6..6779a20 100644 --- a/inputs/PygameInput.py +++ b/inputs/PygameInput.py @@ -1,4 +1,5 @@ -import time, Util +import time +import util.Strings as Strings from operationscore.Input import * import pygame from pygame.locals import * @@ -8,13 +9,13 @@ class PygameInput(Input): def sensingLoop(self): #try: if self['FollowMouse']: - self.respond({Util.location: pygame.mouse.get_pos()}) + self.respond({Strings.LOCATION: pygame.mouse.get_pos()}) return for event in pygame.event.get(): if event.type is KEYDOWN: - self.respond({Util.location: (5,5),'Key': event.key}) + self.respond({Strings.LOCATION: (5,5),'Key': event.key}) if event.type is MOUSEBUTTONDOWN: - self.respond({Util.location: pygame.mouse.get_pos()}) + self.respond({Strings.LOCATION: pygame.mouse.get_pos()}) #except: #raise Exception('Pygame not initialized. Pygame must be \ #initialized.') diff --git a/inputs/TCPInput.py b/inputs/TCPInput.py index acd6243..1517afa 100644 --- a/inputs/TCPInput.py +++ b/inputs/TCPInput.py @@ -1,4 +1,4 @@ -import Util +import util.Strings as Strings from operationscore.Input import * import socket, json, time class TCPInput(Input): @@ -25,7 +25,7 @@ class TCPInput(Input): if self.IS_RESPONDING == 1: # if 'responding', respond to the received data dataDict = json.loads(data) # socketDict = {'data':dataDict, 'address':self.address} - socketDict = {Util.location: (100 * (1 - dataDict['x'] / 10), 25 * (1 + dataDict['y'] / 10))} # like PygameInput + socketDict = {Strings.LOCATION: (100 * (1 - dataDict['x'] / 10), 25 * (1 + dataDict['y'] / 10))} # like PygameInput self.respond(socketDict) else: diff --git a/inputs/TCPInput_backup.py b/inputs/TCPInput_backup.py index 01b6a99..d2c4087 100644 --- a/inputs/TCPInput_backup.py +++ b/inputs/TCPInput_backup.py @@ -1,5 +1,4 @@ import SocketServer -import Util from operationscore.Input import * """ diff --git a/inputs/UDPInput.py b/inputs/UDPInput.py index 5b83792..7d5609e 100644 --- a/inputs/UDPInput.py +++ b/inputs/UDPInput.py @@ -1,4 +1,3 @@ -import Util from operationscore.Input import * import socket class UDPInput(Input): diff --git a/operationscore/Behavior.py b/operationscore/Behavior.py index 3bdf1ec..d48c1d5 100644 --- a/operationscore/Behavior.py +++ b/operationscore/Behavior.py @@ -33,14 +33,26 @@ class Behavior(SmootCoreObject): recursiveInputs) if type(output) != type([]): output = [output] - return (output, recursions) - except: + 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),[]) def addInputs(self, sensorInputs): if type(sensorInputs) == type([]): [self.addInput(sensorInput) for sensorInput in sensorInputs] else: self.addInput(sensorInputs) + #private + def addMapperToResponse(self, responses): + if self['Mapper'] != None: + if type(responses) == type(tuple): + (out, recurs) = responses + return (self.addMapperToResponse(out), self.addMapperToResponse(recurs)) + if type(responses) == type([]): + for r in responses: + r['Mapper'] = self['Mapper'] + return responses + return responses def timeStep(self): #TODO: type checking. clean this up responses = self.processResponse(self.sensorResponseQueue, \ self.recursiveResponseQueue) @@ -54,8 +66,7 @@ class Behavior(SmootCoreObject): if type(outputs) != type([]): outputs = [outputs] try: - return outputs + return self.addMapperToResponse(outputs) #TODO: WTF is up with this? except: pass - #pdb.set_trace() - return outputs + return self.addMapperToResponse(outputs) diff --git a/operationscore/Input.py b/operationscore/Input.py index 2144678..62c4682 100644 --- a/operationscore/Input.py +++ b/operationscore/Input.py @@ -1,4 +1,4 @@ -import threading,time,Util +import threading,time from operationscore.SmootCoreObject import * #Abstract class for inputs. Inheriting classes should call "respond" to raise #their event. Inheriting classes MUST define sensingLoop. Called at the @@ -22,14 +22,10 @@ class Input(SmootCoreObject): self.daemon = True #This kills this thread when the main thread stops def respond(self, eventDict): #if eventDict != []: - #pdb.set_trace() self.parentScope.lock.acquire() self.parentScope.processResponse(self.argDict, eventDict) self.parentScope.lock.release() time.sleep(.001) - def newEvent(self, event): #Mostly just useful for grabbing events from the - #computer running the sim (key presses, clicks etc.) - self.eventQueue.append(event) def parentAlive(self): try: parentAlive = self.parentScope.alive() diff --git a/operationscore/PixelAssembler.py b/operationscore/PixelAssembler.py index b6e35ac..6878f8a 100644 --- a/operationscore/PixelAssembler.py +++ b/operationscore/PixelAssembler.py @@ -1,5 +1,5 @@ from operationscore.SmootCoreObject import * -import Util +import util.Geo as Geo import pdb class PixelAssembler(SmootCoreObject): def init(self): @@ -17,7 +17,7 @@ class PixelAssembler(SmootCoreObject): if newLocation == None: raise Exception('Location cannot be null. layoutFunc not \ defined or improperly defined.') - if Util.dist(newLocation, locations[-1]) > \ + if Geo.dist(newLocation, locations[-1]) > \ self['pixelToPixelSpacing']: raise Exception('Illegal pixel location. Distance \ between adjacent pixels must be less than \ diff --git a/operationscore/PixelEvent.py b/operationscore/PixelEvent.py index 66b6fdf..e2b852a 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 * +import util.ColorOps as color class PixelEvent(SmootCoreObject): def init(self): self.validateArgs('PixelEvent.params') @@ -11,8 +12,16 @@ class PixelEvent(SmootCoreObject): #Returns a new PixelEvent, but with a response scaled by c. def scale(self,c): newDict = dict(self.argDict) - newDict['Color'] = Util.multiplyColor(newDict['Color'], c) + newDict['Color'] = color.multiplyColor(newDict['Color'], c) return self.__class__(newDict) def state(self,timeDelay): pass + @staticmethod + def addPixelEventIfMissing(responseDict): + if not 'PixelEvent' in responseDict: + if 'Color' in responseDict: + color = responseDict['Color'] + else: + raise Exception('Need Color. Probably') + responseDict['PixelEvent'] = StepEvent.generate(300, color) diff --git a/operationscore/PixelMapper.py b/operationscore/PixelMapper.py index bbbfcf4..e3f2515 100644 --- a/operationscore/PixelMapper.py +++ b/operationscore/PixelMapper.py @@ -1,5 +1,4 @@ from operationscore.SmootCoreObject import * -import Util import pdb class PixelMapper(SmootCoreObject): def init(self): diff --git a/operationscore/SmootCoreObject.py b/operationscore/SmootCoreObject.py index 8514b3e..9784aab 100644 --- a/operationscore/SmootCoreObject.py +++ b/operationscore/SmootCoreObject.py @@ -1,12 +1,13 @@ -import Util import pdb import threading import thread +import util.Config as configGetter class SmootCoreObject(threading.Thread): def __init__(self, argDict, skipValidation = False): self.argDict = argDict self.validateArgs(self.className()+'.params') self.lock = thread.allocate_lock() + threading.Thread.__init__(self) self.init() #call init of inheriting class # self.__setitem__ = self.argDict.__setitem__ # self.__getitem__ = self.argDict.__getitem__ @@ -29,7 +30,7 @@ class SmootCoreObject(threading.Thread): def __getiter__(self): return self.argDict.__getiter__() def validateArgs(self, argFileName): - self.validateArgDict(Util.loadParamRequirementDict(argFileName))#util + self.validateArgDict(configGetter.loadParamRequirementDict(argFileName))#util #caches for us, woo! def validateArgDict(self, validationDict): for item in validationDict: diff --git a/pixelcore/Pixel.py b/pixelcore/Pixel.py index ba87dff..f66d0bb 100644 --- a/pixelcore/Pixel.py +++ b/pixelcore/Pixel.py @@ -1,6 +1,7 @@ -import Util +import util.ColorOps as color import pdb from pixelevents.StepEvent import * +import util.TimeOps as clock #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, @@ -24,7 +25,7 @@ class Pixel: #arg #Add a pixelEvent to the list of active events def processInput(self,pixelEvent,zindex): #consider migrating arg to dict - self.events[Util.time()] = (zindex, pixelEvent) + self.events[clock.time()] = (zindex, pixelEvent) def clearAllEvents(self): self.events = {} #Combines all PixelEvents currently active and computes the current color of @@ -37,13 +38,13 @@ class Pixel: if len(self.events) == 0: return (0,0,0) deadEvents = [] - currentTime = Util.time() + currentTime = clock.time() resultingColor = (0,0,0) for eventTime in self.events: #TODO: right color weighting code (zindex,event) = self.events[eventTime] eventResult = event.state(currentTime-eventTime) if eventResult != None: - resultingColor = Util.combineColors(eventResult, resultingColor) + resultingColor = color.combineColors(eventResult, resultingColor) else: deadEvents.append(eventTime) [self.events.pop(event) for event in deadEvents] diff --git a/pixelcore/PixelStrip.py b/pixelcore/PixelStrip.py index c82a87a..fceff8e 100644 --- a/pixelcore/PixelStrip.py +++ b/pixelcore/PixelStrip.py @@ -1,8 +1,9 @@ from pixelcore.Pixel import * +import util.Strings as Strings +import util.Geo as Geo from pixelevents.StepEvent import * import pygame import math -import Util import pdb #Python class representing a single Pixel strip (usually 50 Pixels) class PixelStrip: @@ -21,7 +22,7 @@ class PixelStrip: [l.turnOnFor(time) for l in self.pixels] #TODO: add test-on method to #pixels def respond(self, responseInfo): - location = responseInfo[Util.location] + location = responseInfo[Strings.LOCATION] if not 'PixelEvent' in responseInfo: if 'Color' in responseInfo: color = responseInfo['Color'] @@ -32,7 +33,7 @@ class PixelStrip: pixel.processInput(responseInfo['PixelEvent'], 0) #TODO: z-index def getPixelNearest(self, location): - dists = [(Util.dist(location, pixel.location), pixel) for pixel in self.pixels] + 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 92805a8..da03ad2 100644 --- a/pixelcore/Screen.py +++ b/pixelcore/Screen.py @@ -1,5 +1,10 @@ from pixelcore.Pixel import * from pixelcore.PixelStrip import * +from operationscore.PixelEvent import * +from operationscore.PixelMapper import * +import util.Search as Search +import util.ComponentRegistry as compReg +import util.Strings as Strings import itertools #Class representing a collection of Pixels grouped into PixelStrips. Needs a #PixelMapper, currently set via setMapper by may be migrated into the argDict. @@ -17,8 +22,8 @@ class Screen: self.computeXSortedPixels() #Returns (pixelIndex, pixel). Does a binary search. def pixelsInRange(self, minX, maxX): - minIndex = Util.find_ge(self.xPixelLocs, minX) - maxIndex = Util.find_le(self.xPixelLocs, maxX)+1 + minIndex = Search.find_ge(self.xPixelLocs, minX) + maxIndex = Search.find_le(self.xPixelLocs, maxX)+1 return self.xSortedPixels[minIndex:maxIndex] def computeXSortedPixels(self): for pixel in self: @@ -27,8 +32,6 @@ class Screen: self.xPixelLocs = [p[0] for p in self.xSortedPixels] def render(self, surface): [lS.render(surface) for lS in self.pixelStrips] - def setMapper(self, mapper): - self.mapper = mapper def allOn(self): [lS.allOn(-1) for lS in self.pixelStrips] def __iter__(self): #the iterator of all our pixel strips chained togther @@ -68,9 +71,14 @@ class Screen: #[strip.respond(dict(responseInfo)) for strip in self.pixelStrips] if type(responseInfo) != type(dict()): pass - #pdb.set_trace() - pixelWeightList = self.mapper.mapEvent(responseInfo['Location'], self) - Util.addPixelEventIfMissing(responseInfo) + if 'Mapper' in responseInfo: + mapper = compReg.getComponent(responseInfo['Mapper']) + else: + mapper = compReg.getComponent(Strings.DEFAULT_MAPPER) + #if type(mapper) != type(PixelMapper): + # raise Exception('No default mapper specified.') + pixelWeightList = mapper.mapEvent(responseInfo['Location'], self) + PixelEvent.addPixelEventIfMissing(responseInfo) for (pixel, weight) in pixelWeightList: pixel.processInput(responseInfo['PixelEvent'].scale(weight), 0) #TODO: z-index diff --git a/pixelevents/DecayEvent.py b/pixelevents/DecayEvent.py index 9a7c600..0b4c820 100644 --- a/pixelevents/DecayEvent.py +++ b/pixelevents/DecayEvent.py @@ -1,5 +1,6 @@ from operationscore.PixelEvent import * -import Util, math +import math +from util.ColorOps import * class DecayEvent(PixelEvent): def initEvent(self): self['Coefficient'] = abs(self['Coefficient']) @@ -8,7 +9,7 @@ class DecayEvent(PixelEvent): decay = math.exp(timeDelay*-1*self['Coefficient']) if self['DecayType'] == 'Proportional': decay = float(self['Coefficient']) / timeDelay - color = Util.multiplyColor(self['Color'], decay) + color = multiplyColor(self['Color'], decay) return color if sum(color) > 5 else None @staticmethod def generate(decayType, coefficient, color): diff --git a/pixelmappers/GaussianMapper.py b/pixelmappers/GaussianMapper.py index 04bd447..8755acf 100644 --- a/pixelmappers/GaussianMapper.py +++ b/pixelmappers/GaussianMapper.py @@ -1,13 +1,13 @@ from operationscore.PixelMapper import * -import Util +import util.Geo as Geo class GaussianMapper(PixelMapper): def mappingFunction(self, eventLocation, screen): returnPixels = [] #TODO: consider preallocation and trimming [x,y] = eventLocation for (x,pixel) in screen.pixelsInRange(x-self['CutoffDist'], \ x+self['CutoffDist']): - pixelDist = Util.dist(pixel.location, eventLocation) + pixelDist = Geo.dist(pixel.location, eventLocation) if pixelDist < self['CutoffDist']: - w = Util.gaussian(pixelDist, self['Height'], 0, self['Width']) + w = Geo.gaussian(pixelDist, self['Height'], 0, self['Width']) returnPixels.append((pixel, w)) return returnPixels diff --git a/pixelmappers/SimpleMapper.py b/pixelmappers/SimpleMapper.py index 7d730f1..bc51cf9 100644 --- a/pixelmappers/SimpleMapper.py +++ b/pixelmappers/SimpleMapper.py @@ -1,14 +1,29 @@ from operationscore.PixelMapper import * -import Util +import util.Geo as Geo class SimpleMapper(PixelMapper): def mappingFunction(self, eventLocation, screen): - bestDist = 10**10 #don't kill me, I'm lazy - bestPixel = None - for pixel in screen: - pixelDist = Util.dist(pixel.location, eventLocation) - if pixelDist < bestDist: - bestPixel = pixel - bestDist = pixelDist - return [(bestPixel,1)] - + if type(eventLocation) == type(tuple()): + bestDist = 10**10 #don't kill me, I'm lazy + bestPixel = None + for pixel in screen: + pixelDist = Geo.dist(pixel.location, eventLocation) + if pixelDist < bestDist: + bestPixel = pixel + bestDist = pixelDist + return [(bestPixel,1)] + elif type(type(str)): + #[{x}>5,{y}<k] + #TODO: we should probably encapsulate this somewhere + ret = [] + eventLocation = eventLocation.replace('{x}', 'pixel.location[0]') + eventLocation = eventLocation.replace('{y}', 'pixel.location[1]') + for pixel in screen: + try: + pixelValid = sum(eval(eventLocation)) == len(eval(eventLocation)) #TODO: some + #optimizations possible. This might be slow in the long run + if pixelValid: + ret.append((pixel, 1)) + except: + raise Exception('Bad event condition') + return ret diff --git a/renderers/IndoorRenderer.py b/renderers/IndoorRenderer.py index efe2b3a..c13d11f 100644 --- a/renderers/IndoorRenderer.py +++ b/renderers/IndoorRenderer.py @@ -1,15 +1,15 @@ from operationscore.Renderer import * -import socket, Util -import pdb -kinetPort = 6038 +import util.PacketComposition as composer +import util.NetworkOps as network +import socket,pdb +port = 6038 +#Renderer for a Specific Light System. class IndoorRenderer(Renderer): def initRenderer(self): - #pdb.set_trace() self.stripLocations = {} #Dict that stores info necessary to render to #strips - self.sockets = {} #dict of (IP,port)->Socket + self.sockets = {} #dict of (IP)->Socket #a strip -# g self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) powerSupplies = self.argDict['PowerSupply'] if not type(powerSupplies) == type([]): powerSupplies = [powerSupplies] @@ -20,12 +20,15 @@ class IndoorRenderer(Renderer): self.stripLocations[stripId] = (ip, \ stripsInPowerSupply[stripId]) def render(self, lightSystem): - for pixelStrip in lightSystem.pixelStrips: - stripId = pixelStrip.argDict['Id'] - (ip, port) = self.stripLocations[stripId] - if not ip in self.sockets: #do we have a socket to this - #strip? if not, spin off a new one - self.sockets[ip] = Util.getConnectedSocket(ip,kinetPort) - packet = Util.composePixelStripPacket(pixelStrip, port) - self.sockets[ip].send(packet, 0x00) + try: + for pixelStrip in lightSystem.pixelStrips: + stripId = pixelStrip.argDict['Id'] + (ip, port) = self.stripLocations[stripId] + if not ip in self.sockets: #do we have a socket to this + #strip? if not, spin off a new one + self.sockets[ip] = network.getConnectedSocket(ip,port) + packet = composer.composePixelStripPacket(pixelStrip, port) + self.sockets[ip].send(packet, 0x00) + except: + pass #Rendering error. Log it. LOG diff --git a/util/ColorOps.py b/util/ColorOps.py new file mode 100644 index 0000000..b0d64a7 --- /dev/null +++ b/util/ColorOps.py @@ -0,0 +1,11 @@ +import random +def randomColor(): + return [random.randint(0,255) for i in range(3)] +def chooseRandomColor(colorList): + return random.choice(colorList) +def safeColor(c): + return [min(channel,255) for channel in c] +def combineColors(c1,c2): + return safeColor([c1[i]+c2[i] for i in range(min(len(c1),len(c2)))]) +def multiplyColor(color, percent): + return safeColor([channel*(percent) for channel in color]) diff --git a/util/ComponentRegistry.py b/util/ComponentRegistry.py new file mode 100644 index 0000000..119ce18 --- /dev/null +++ b/util/ComponentRegistry.py @@ -0,0 +1,43 @@ +import pdb +#component registry, a singleton +import thread +#class ComponentRegistry: +# def __init__(self): +# self.regDict = {} +# @staticmethod +# def getRegistry(self): +# if self.instance == None: +# self.instance = self.__class__() +# return self.instance +# def registerComponent(component, cid=None): +# if cid != None: +# globals()['Registry'][cid] = component +# else: +# try: +# cid = component['Id'] +# globals()['Registry'][cid] = component +# except: +# raise Exception('Must specify Id, component did not store it') +#def registerDefault( +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 initRegistry(): + globals()['Registry'] = {} +def registerComponent(component, cid=None): + if cid != None: + globals()['Registry'][cid] = component + else: + try: + cid = component['Id'] + globals()['Registry'][cid] = component + except: + raise Exception('Must specify Id, component did not store it') +#def registerDefault( +def removeComponent(cid): + globals()['Registry'].pop(cid) +def getComponent(cid): + return globals()['Registry'][cid] diff --git a/util/Config.py b/util/Config.py new file mode 100644 index 0000000..f80b7b2 --- /dev/null +++ b/util/Config.py @@ -0,0 +1,53 @@ +from xml.etree.ElementTree import ElementTree +classArgsMem = {} +CONFIG_PATH = 'config/' +def loadParamRequirementDict(className): + if not className in classArgsMem: #WOO CACHING + classArgsMem[className] = fileToDict(CONFIG_PATH + className) + return classArgsMem[className] +def loadConfigFile(fileName): #TODO: error handling etc. + #try: + fileName = CONFIG_PATH + fileName + if '.params' in fileName: + return fileToDict(fileName) + if '.xml' in fileName: + config = ElementTree() + config.parse(fileName) + return config + #except: + return None +def fileToDict(fileName): + fileText = '' + try: + print 'File Read' + with open(fileName) as f: + for line in f: + fileText += line.rstrip('\n').lstrip('\t') + ' ' + except IOError: + return {} + if fileText == '': + return {} + return eval(fileText) +#parses arguments into python objects if possible, otherwise leaves as strings +def generateArgDict(parentNode, recurse=False): + args = {} + for arg in parentNode.getchildren(): + key = arg.tag + if arg.getchildren() != []: + 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) + if key in args: #build of lists of like-elements + if type(args[key]) != type([]): + args[key] = [args[key]] + args[key].append(value) + else: + args[key]=value + #if we should be a list but we aren't: + if len(args.keys()) == 1 and recurse: + return args[args.keys()[0]] + return args diff --git a/util/Geo.py b/util/Geo.py new file mode 100644 index 0000000..885c585 --- /dev/null +++ b/util/Geo.py @@ -0,0 +1,15 @@ +#Geometry code +import math +from bisect import * +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 +print pointWithinBoundingBox((118,21), (10,8,298,42)) +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(sum([(l1[i]-l2[i])**2 for i in range(len(l1))])) diff --git a/util/NetworkOps.py b/util/NetworkOps.py new file mode 100644 index 0000000..0404975 --- /dev/null +++ b/util/NetworkOps.py @@ -0,0 +1,8 @@ +import socket +def getConnectedSocket(ip,port): + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + sock.connect((ip, port)) + except: + print 'network down' + return sock diff --git a/util/PacketComposition.py b/util/PacketComposition.py new file mode 100644 index 0000000..2563c61 --- /dev/null +++ b/util/PacketComposition.py @@ -0,0 +1,57 @@ +import struct +VERSION = 0x0001 +MAGIC = 0x4adc0104 +MOREMAGIC = 0xdeadbeef +DEEPMAGIC = 0xc001d00d +MAGICHASH = 0x69000420 +PORTOUT = 0x0108 +UNI = 0 +kinetDict = {'flags': 0, 'startcode': 0, 'pad':0} +def composePixelStripData(pixelStrip): + packet = bytearray() + for light in pixelStrip: + color = light.state() + for channel in color: #skip the last value, its an + #alpha value + packet.append(struct.pack('B', channel)) + return packet +# packet = [0]*len(pixelStrip.pixels)*3 #preallocate for speed +# for i in range(len(pixelStrip.pixels)): +#color = pixelStrip.pixels[i].state() +#packet[i:i+2] = color +# return bytearray(packet) +def composePixelStripPacket(pixelStrip,port): + packet = bytearray() + data = composePixelStripData(pixelStrip) + subDict = dict(kinetDict) + subDict['len'] = 38000 #I have no idea why this works. + subDict['port'] = port + packet.extend(kinetPortOutPacket(subDict)) + packet.append(0x0) + packet.extend(data) + return packet +def kinetHeader(): + header = bytearray() + header.extend(struct.pack('L', MAGIC)) + header.extend(struct.pack('H', VERSION)) + header.extend(struct.pack('H', PORTOUT)) + header.extend(struct.pack('L', 0)) + return header +def kinetPortOut(): + header = kinetHeader() + header.extend(struct.pack('L', UNI)) + return header +def kinetPortOutPayload(argDict): + payload = bytearray() + payload.extend(struct.pack('B', argDict['port'])) + #payload.append(0x00) #somepadding? lolwtf. + 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 kinetPortOutPacket(payloadArgs): + packet = bytearray() + packet.extend(kinetPortOut()) + packet.extend(kinetPortOutPayload(payloadArgs)) + return packet diff --git a/util/Search.py b/util/Search.py new file mode 100644 index 0000000..25882da --- /dev/null +++ b/util/Search.py @@ -0,0 +1,8 @@ +from bisect import * +def find_le(a, 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' + return bisect_left(a, x) diff --git a/util/Strings.py b/util/Strings.py new file mode 100644 index 0000000..81c34f2 --- /dev/null +++ b/util/Strings.py @@ -0,0 +1,2 @@ +LOCATION = 'Location' +DEFAULT_MAPPER = 'DefaultPixelMapper' diff --git a/util/TimeOps.py b/util/TimeOps.py new file mode 100644 index 0000000..dcd5038 --- /dev/null +++ b/util/TimeOps.py @@ -0,0 +1,19 @@ +import time as clock +def time(): + return clock.time()*1000 #all times in MS +class Stopwatch: + def __init__(self): + self.running = False + self.startTime = -1 + self.stopTime = -1 + def start(self): + self.startTime = time() + self.running = True + def elapsed(self): + if self.running: + return time()-self.startTime + else: + return self.stopTime - self.startTime + def stop(self): + self.stopTime = time() + self.running = False diff --git a/util/__init__.py b/util/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/util/__init__.py |