From b042647b68abdc82490ca6e059993b8eba28904c Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Wed, 24 Nov 2010 01:09:12 -0500 Subject: Refactoring complete! Made modules/packages as appropriate. Finally. --- Behavior.py | 33 ------ DebugBehavior.py | 8 -- DecayEvent.py | 12 --- EchoBehavior.py | 12 --- IndoorRenderer.py | 31 ------ Input.py | 46 --------- LayoutEngine.py | 30 ------ LightInstallation.py | 11 +- LineLayout.py | 5 - Pixel.py | 44 -------- PixelEvent.py | 13 --- PixelStrip.py | 40 ------- PygameInput.py | 17 --- PygameRenderer.py | 20 ---- Renderer.py | 12 --- Screen.py | 31 ------ SmootCoreObject.py | 23 ----- StepEvent.py | 14 --- TCPInput.py | 51 --------- UDPInput.py | 16 --- Util.py | 13 ++- ZigzagLayout.py | 45 -------- behaviors/DebugBehavior.py | 8 ++ behaviors/EchoBehavior.py | 12 +++ behaviors/__init__.py | 0 config/.LightInstallationConfig.xml.swp | Bin 0 -> 12288 bytes config/Behavior.params | 1 + config/DecayEvent.params | 3 + config/Input.params | 2 + config/LayoutEngine.params | 6 ++ config/LightInstallationConfig.xml | 94 +++++++++++++++++ config/PixelEvent.params | 1 + config/RendererConfig.xml | 14 +++ config/StepEvent.params | 1 + config/TestXML.xml | 23 +++++ config/UDPTestConfig.xml | 24 +++++ docs/designDocs.pdf | Bin 0 -> 129631 bytes docs/designDocs.tex | 178 ++++++++++++++++++++++++++++++++ inputs/PygameInput.py | 17 +++ inputs/TCPInput.py | 52 ++++++++++ inputs/UDPInput.py | 17 +++ layouts/LineLayout.py | 5 + layouts/ZigzagLayout.py | 45 ++++++++ layouts/__init__.py | 0 operationscore/Behavior.py | 33 ++++++ operationscore/Input.py | 46 +++++++++ operationscore/LayoutEngine.py | 30 ++++++ operationscore/PixelEvent.py | 13 +++ operationscore/Renderer.py | 12 +++ operationscore/SmootCoreObject.py | 23 +++++ operationscore/__init__.py | 0 pixelcore/Pixel.py | 44 ++++++++ pixelcore/PixelStrip.py | 40 +++++++ pixelcore/Screen.py | 31 ++++++ pixelcore/__init__.py | 0 pixelevents/DecayEvent.py | 12 +++ pixelevents/StepEvent.py | 14 +++ pixelevents/__init__.py | 0 renderers/IndoorRenderer.py | 31 ++++++ renderers/PygameRenderer.py | 20 ++++ renderers/__init__.py | 0 61 files changed, 868 insertions(+), 511 deletions(-) delete mode 100644 Behavior.py delete mode 100644 DebugBehavior.py delete mode 100644 DecayEvent.py delete mode 100644 EchoBehavior.py delete mode 100644 IndoorRenderer.py delete mode 100644 Input.py delete mode 100644 LayoutEngine.py delete mode 100644 LineLayout.py delete mode 100644 Pixel.py delete mode 100644 PixelEvent.py delete mode 100644 PixelStrip.py delete mode 100644 PygameInput.py delete mode 100644 PygameRenderer.py delete mode 100644 Renderer.py delete mode 100644 Screen.py delete mode 100644 SmootCoreObject.py delete mode 100644 StepEvent.py delete mode 100644 TCPInput.py delete mode 100644 UDPInput.py delete mode 100644 ZigzagLayout.py create mode 100644 behaviors/DebugBehavior.py create mode 100644 behaviors/EchoBehavior.py create mode 100644 behaviors/__init__.py create mode 100644 config/.LightInstallationConfig.xml.swp create mode 100644 config/Behavior.params create mode 100644 config/DecayEvent.params create mode 100644 config/Input.params create mode 100644 config/LayoutEngine.params create mode 100644 config/LightInstallationConfig.xml create mode 100644 config/PixelEvent.params create mode 100644 config/RendererConfig.xml create mode 100644 config/StepEvent.params create mode 100644 config/TestXML.xml create mode 100644 config/UDPTestConfig.xml create mode 100644 docs/designDocs.pdf create mode 100644 docs/designDocs.tex create mode 100644 inputs/PygameInput.py create mode 100644 inputs/TCPInput.py create mode 100644 inputs/UDPInput.py create mode 100644 layouts/LineLayout.py create mode 100644 layouts/ZigzagLayout.py create mode 100644 layouts/__init__.py create mode 100644 operationscore/Behavior.py create mode 100644 operationscore/Input.py create mode 100644 operationscore/LayoutEngine.py create mode 100644 operationscore/PixelEvent.py create mode 100644 operationscore/Renderer.py create mode 100644 operationscore/SmootCoreObject.py create mode 100644 operationscore/__init__.py create mode 100644 pixelcore/Pixel.py create mode 100644 pixelcore/PixelStrip.py create mode 100644 pixelcore/Screen.py create mode 100644 pixelcore/__init__.py create mode 100644 pixelevents/DecayEvent.py create mode 100644 pixelevents/StepEvent.py create mode 100644 pixelevents/__init__.py create mode 100644 renderers/IndoorRenderer.py create mode 100644 renderers/PygameRenderer.py create mode 100644 renderers/__init__.py diff --git a/Behavior.py b/Behavior.py deleted file mode 100644 index e25f7be..0000000 --- a/Behavior.py +++ /dev/null @@ -1,33 +0,0 @@ -#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 -#spawned during the last time step. Inheriting classes MUST define -#processResponse. processResponse should return a list of dictionaries which -#define the properties of the light response. They must give a location and -#color. They may define a function pointer which defines a custom mapping. -#[More on this later. Bug Russell if you want to do it]. -#recursiveResponse to queue a input on the next iteration with a dictionary -#argument. This will be passed in via recursive inputs. -import pdb -from SmootCoreObject import SmootCoreObject -#timeStep is called on every iteration of the LightInstallation -#addInput is called on each individual input received, and the inputs queue -class Behavior(SmootCoreObject): - def init(self): - self.validateArgs('Behavior.params') - if type(self['Inputs']) != type([]): - self['Inputs'] = [self['Inputs']] - self.recursiveResponseQueue = [] - self.sensorResponseQueue = [] - self.outGoingQueue = [] - def processResponse(self, sensorInputs, recursiveInputs): - pass - def addInput(self, sensorInputs): - self.sensorResponseQueue.append(sensorInputs) - def recursiveReponse(self, args): - self.responseQueue.append(args) - def timeStep(self): - responses = self.processResponse(self.sensorResponseQueue, \ - self.recursiveResponseQueue) - self.sensorResponseQueue = [] - self.recursiveResponseQueue = [] - return responses diff --git a/DebugBehavior.py b/DebugBehavior.py deleted file mode 100644 index f0758be..0000000 --- a/DebugBehavior.py +++ /dev/null @@ -1,8 +0,0 @@ -from Behavior import Behavior -import Util -import pdb -class DebugBehavior(Behavior): - def processResponse(self, sensorInputs, recursiveInputs): - if sensorInputs != []: - print 'Sensor Inputs: ', sensorInputs - return [] diff --git a/DecayEvent.py b/DecayEvent.py deleted file mode 100644 index 4dbe3cd..0000000 --- a/DecayEvent.py +++ /dev/null @@ -1,12 +0,0 @@ -from PixelEvent import PixelEvent -import Util, math -class DecayEvent(PixelEvent): - def initEvent(self): - self.validateArgs('DecayEvent.params') - self['Coefficient'] = abs(self['Coefficient']) - def state(self,timeDelay): - if self['DecayType'] == 'Exponential': - decay = math.exp(timeDelay*-1*self['Coefficient']) - if self['DecayType'] == 'Proportional': - decay = float(self['Coefficient']) / timeDelay - return Util.multiplyColor(self['Color'], decay) diff --git a/EchoBehavior.py b/EchoBehavior.py deleted file mode 100644 index 6d0a79b..0000000 --- a/EchoBehavior.py +++ /dev/null @@ -1,12 +0,0 @@ -from Behavior import Behavior -import Util -import pdb -class EchoBehavior(Behavior): - def processResponse(self, sensorInputs, recursiveInputs): - ret = [] - for sensory in sensorInputs: - outDict = {} - outDict[Util.location] = sensory[Util.location] - outDict['Color'] = (255,0,0) - ret.append(outDict) - return ret diff --git a/IndoorRenderer.py b/IndoorRenderer.py deleted file mode 100644 index b42c337..0000000 --- a/IndoorRenderer.py +++ /dev/null @@ -1,31 +0,0 @@ -from Renderer import Renderer -import socket, Util -import pdb -kinetPort = 6038 -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 - #a strip -# g self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - powerSupplies = self.argDict['PowerSupply'] - if not type(powerSupplies) == type([]): - powerSupplies = [powerSupplies] - for powerSupply in powerSupplies: - ip = powerSupply['IP'] - stripsInPowerSupply = powerSupply['PortMapping'] - for stripId in stripsInPowerSupply: - 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) - diff --git a/Input.py b/Input.py deleted file mode 100644 index 1ba4528..0000000 --- a/Input.py +++ /dev/null @@ -1,46 +0,0 @@ -import threading,time,Util -#Abstract class for inputs. Inheriting classes should call "respond" to raise -#their event. Inheriting classes MUST define sensingLoop. Called at the -#interval specified in RefreshInterval while the input is active. For example, if you are writing -#webserver, this is where the loop should go. -#Inheriting classes MAY define inputInit. This is called before the loop -#begins. -import pdb -class Input(threading.Thread): - #Event scope is a function pointer the function that will get called when - #an Parent is raised. - def __init__(self, argDict): - self.eventQueue = [] - self.parentScope = argDict['parentScope'] - self.argDict = argDict - if not 'InputId' in argDict: - raise Exception('InputId must be defined in config xml') - if not 'RefreshInterval' in argDict: - print 'RefreshInterval not defined. Defaulting to .5s.' - self.argDict['RefreshInterval'] = 500 - self.inputInit() - threading.Thread.__init__(self) - self.daemon = True #This kills this thread when the main thread stops - def respond(self, eventDict): - #if eventDict != []: - #pdb.set_trace() - self.parentScope.processResponse(self.argDict, eventDict) - 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() - return parentAlive - except: - return False - def run(self): - while self.parentAlive(): - time.sleep(self.argDict['RefreshInterval']/float(1000)) - self.sensingLoop() - def sensingLoop(self): - pass - def inputInit(self): - pass - - diff --git a/LayoutEngine.py b/LayoutEngine.py deleted file mode 100644 index 153167e..0000000 --- a/LayoutEngine.py +++ /dev/null @@ -1,30 +0,0 @@ -from SmootCoreObject import SmootCoreObject -import Util -import pdb -class LayoutEngine(SmootCoreObject): - def init(self): - self.validateArgs('LayoutEngine.params') - self.initLayout() - def layoutFunc(self, lastLocation): #Must be defined by inheriting class. - #Returns tuple pair (x,y) - pass - def getPixelLocations(self): #returns a complete list of locations of Pixels - #for a strip - locations = [self.argDict['originLocation']] - for pixelIndex in range(self['numPixels']-1): #-1 because origin - #already exists - newLocation = self.layoutFunc(locations[-1]) - if newLocation == None: - raise Exception('Location cannot be null. layoutFunc not \ - defined or improperly defined.') - if Util.dist(newLocation, locations[-1]) > \ - self['pixelToPixelSpacing']: - raise Exception('Illegal pixel location. Distance \ - between adjacent pixels must be less than \ - pixelToPixelSpacing.') - locations.append(newLocation) - return locations - def initLayout(self): - pass - def getStripArgs(self): #TODO: triage and remove - return self.argDict diff --git a/LightInstallation.py b/LightInstallation.py index 0c24981..3fcdcd5 100644 --- a/LightInstallation.py +++ b/LightInstallation.py @@ -1,6 +1,6 @@ from xml.etree.ElementTree import ElementTree -from Screen import Screen -from PixelStrip import PixelStrip +from pixelcore.Screen import * +from pixelcore.PixelStrip import * import pdb, sys, time, Util from pygame.locals import * #Python class to instantiate and drive a Screen through different patterns, @@ -9,9 +9,8 @@ class LightInstallation: def __init__(self, configFileName): self.inputs = {} #dict of inputs and their bound behaviors, keyed by InputId self.behaviors = {} - config = ElementTree() - config.parse(configFileName) self.screen = Screen() + config = Util.loadConfigFile(configFileName) rendererConfig = config.find('RendererConfiguration') layoutConfig = config.find('LayoutConfiguration') inputConfig = config.find('InputConfiguration') @@ -40,8 +39,8 @@ class LightInstallation: components = [] if config != None: for configItem in config.getchildren(): - className = configItem.find('Class').text - exec('from ' + className + ' import ' + className) + [module,className] = configItem.find('Class').text.split('.') + exec('from ' + module+'.'+className + ' import *') args = Util.generateArgDict(configItem.find('Args')) args['parentScope'] = self components.append(eval(className+'(args)')) #TODO: doesn't error diff --git a/LineLayout.py b/LineLayout.py deleted file mode 100644 index f5cb5ce..0000000 --- a/LineLayout.py +++ /dev/null @@ -1,5 +0,0 @@ -from LayoutEngine import LayoutEngine -#Simple layout class that simply makes a line of LEDs -class LineLayout(LayoutEngine): - def layoutFunc(self, lastLocation): - return (lastLocation[0]+self.argDict['spacing'], lastLocation[1]) diff --git a/Pixel.py b/Pixel.py deleted file mode 100644 index 71e77ed..0000000 --- a/Pixel.py +++ /dev/null @@ -1,44 +0,0 @@ -import Util -import pdb -from StepEvent import StepEvent -#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 cue. If a member returns none, -#it is removed from the queue. Otherwise, its value added to the Pixels color -#weighted by z-index. -class Pixel: - radius = 2 - timeOff = -1 - def __init__(self, location, color): - self.location = location - self.color = color - self.events = {} - def turnOn(self): - self.turnOnFor(-1) - def turnOnFor(self, time): - event = StepEvent.generate(time, (255,0,255)) #TODO: Move color to - self.processInput(event, 0) - #arg - def processInput(self,pixelEvent,zindex): #consider migrating arg to dict - self.events[Util.time()] = (zindex, pixelEvent) - def clearAllEvents(self): - self.events = {} - def state(self): - deadEvents = [] - currentTime = Util.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) - print resultingColor - else: - deadEvents.append(eventTime) - [self.events.pop(event) for event in deadEvents] - if sum(resultingColor) > 0: - print resultingColor - return tuple(resultingColor) - def __str__(self): - return 'Loc: ' + str(self.location) - diff --git a/PixelEvent.py b/PixelEvent.py deleted file mode 100644 index a932e23..0000000 --- a/PixelEvent.py +++ /dev/null @@ -1,13 +0,0 @@ -#Class defining a light response. Inheriting classes should define lightState, -#which should return a color, or None if the response is complete. Consider -#requiring a generate event. -from SmootCoreObject import SmootCoreObject -class PixelEvent(SmootCoreObject): - def init(self): - self.validateArgs('PixelEvent.params') - self.initEvent() - def initEvent(self): - pass - def state(self,timeDelay): - pass - diff --git a/PixelStrip.py b/PixelStrip.py deleted file mode 100644 index 7be9528..0000000 --- a/PixelStrip.py +++ /dev/null @@ -1,40 +0,0 @@ -from Pixel import Pixel -from StepEvent import StepEvent -import pygame -import math -import Util -import pdb -#Python class representing a single Pixel strip (usually 50 Pixels) -class PixelStrip: - def __init__(self, layoutEngine): - self.initStrip(layoutEngine) - self.argDict = layoutEngine.getStripArgs() - def initStrip(self, layoutEngine): - pixelLocations = layoutEngine.getPixelLocations() - self.pixels = [Pixel(l, (0,0,0)) for l in pixelLocations] - def __iter__(self): - return self.pixels.__iter__() - def render(self, surface): - [l.render(surface) for l in self.pixels] - #step - def allOn(self, time): - [l.turnOnFor(time) for l in self.pixels] #TODO: add test-on method to - #pixels - def respond(self, responseInfo): - print 'PixelEvent', responseInfo - location = responseInfo[Util.location] - if not 'PixelEvent' in responseInfo: - if 'Color' in responseInfo: - color = responseInfo['Color'] - else: - raise Exception('Need Color. Probably') - responseInfo['PixelEvent'] = StepEvent.generate(300, color) - (dist, pixel) = self.getPixelNearest(location) - 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.sort() - return dists[0] - #just for now. - diff --git a/PygameInput.py b/PygameInput.py deleted file mode 100644 index 12ae7f6..0000000 --- a/PygameInput.py +++ /dev/null @@ -1,17 +0,0 @@ -import time, Util -from Input import Input -import pygame -from pygame.locals import * -#This class processes input from an already running pygame instance and passes -#it to the parent. This class requires an already running pygame instance. -class PygameInput(Input): - def sensingLoop(self): - #try: - for event in pygame.event.get(): - if event.type is KEYDOWN: - self.respond({Util.location: (5,5),'Key': event.key}) - if event.type is MOUSEBUTTONDOWN: - self.respond({Util.location: pygame.mouse.get_pos()}) - #except: - #raise Exception('Pygame not initialized. Pygame must be \ - #initialized.') diff --git a/PygameRenderer.py b/PygameRenderer.py deleted file mode 100644 index 81740c7..0000000 --- a/PygameRenderer.py +++ /dev/null @@ -1,20 +0,0 @@ -from Renderer import Renderer -import pygame -from pygame.locals import * -import pdb -class PygameRenderer(Renderer): - def initRenderer(self): - pygame.init() - self.screen = pygame.display.set_mode((1300,50)) - self.background = pygame.Surface(self.screen.get_size()) - self.background = self.background.convert() - self.background.fill(Color('Black')) - def render(self, lightSystem): - self.background.fill(Color('Black')) - #print 'drawing color:',light.color - for light in lightSystem: - pygame.draw.circle(self.background, light.state(), light.location, \ - light.radius) - - self.screen.blit(self.background, (0,0)) - pygame.display.flip() diff --git a/Renderer.py b/Renderer.py deleted file mode 100644 index 2297f1f..0000000 --- a/Renderer.py +++ /dev/null @@ -1,12 +0,0 @@ -#Renderer abstract class. Doesn't do much now, but might do more later. -#Inheriting classes MUST define render which takes a light system and renders it. -#Inheriting classes may define initRenderer which is called after the dictionary -#is pulled from config. -from SmootCoreObject import SmootCoreObject -class Renderer(SmootCoreObject): - def init(self): - self.initRenderer() - def render(lightSystem): - pass - def initRenderer(self): - pass diff --git a/Screen.py b/Screen.py deleted file mode 100644 index fe9abc7..0000000 --- a/Screen.py +++ /dev/null @@ -1,31 +0,0 @@ -from Pixel import Pixel -from PixelStrip import PixelStrip -import itertools -class Screen: - def __init__(self): - self.responseQueue = [] - self.pixelStrips = [] - def addStrip(self, lS): - self.pixelStrips.append(lS) - def render(self, surface): - [lS.render(surface) for lS in self.pixelStrips] - def allOn(self): - [lS.allOn(-1) for lS in self.pixelStrips] - def __iter__(self): #the iterator of all our pixel strips chained togther - return itertools.chain(*[strip.__iter__() for strip in self.pixelStrips]) - #increment time -- This processes all queued responses. Responses generated - #during this period are added to the queue that will be processed on the next - #time step. - def timeStep(self): - tempQueue = list(self.responseQueue) - self.responseQueue = [] - for response in tempQueue: - self.processResponse(response) - #public - def respond(self, responseInfo): - self.responseQueue.append(responseInfo) - #private - def processResponse(self, responseInfo): #we need to make a new dict for - #each to prevent interference - [strip.respond(dict(responseInfo)) for strip in self.pixelStrips] - diff --git a/SmootCoreObject.py b/SmootCoreObject.py deleted file mode 100644 index 74e1a9a..0000000 --- a/SmootCoreObject.py +++ /dev/null @@ -1,23 +0,0 @@ -import Util -import pdb -class SmootCoreObject: - def __init__(self, argDict): - self.argDict = argDict - self.init() #call init of inheriting class - def init(self): - pass - def __setitem__(self,k, item): - self.argDict[k] = item - def __getitem__(self, item): - if item in self.argDict: - return self.argDict[item] - else: - return None - def __getiter__(self): - return self.argDict.__getiter__() - def validateArgs(self, argFileName): - self.validateArgDict(Util.fileToDict(argFileName)) - def validateArgDict(self, validationDict): - for item in validationDict: - if not item in self.argDict: - raise Exception(validationDict[item]) diff --git a/StepEvent.py b/StepEvent.py deleted file mode 100644 index aca933b..0000000 --- a/StepEvent.py +++ /dev/null @@ -1,14 +0,0 @@ -from PixelEvent import PixelEvent -class StepEvent(PixelEvent): - def initEvent(self): - self.validateArgs('StepEvent.params') - def state(self,timeDelay): - if timeDelay < self['LightTime'] or self['LightTime'] == -1: - return self['Color'] - else: - return None - @staticmethod - def generate(onTime, color): - args = {'LightTime': onTime, 'Color': color} - return StepEvent(args) - diff --git a/TCPInput.py b/TCPInput.py deleted file mode 100644 index 903b6f0..0000000 --- a/TCPInput.py +++ /dev/null @@ -1,51 +0,0 @@ -import SocketServer -import Util, Input - -""" -A rough sketch about how a TCP socket server receives data from the phone (or other stuff). -Some corrections are probably needed from Russell. -Looks good to me -- not really the way I envisioned it, but since the server -we're using has a built in loop. When we call the reponse method to pass the -data up the pipe, we should use the sensingLoop so that everything stays -thread-safe. -""" -class TCPInput(Input.Input): - class InputTCPHandler(SocketServer.BaseRequestHandler): - def handle(self): - # get data from the TCP socket connected to the client - self.data = self.request.recv(1024).strip() - print "%s wrote:" % self.client_address[0] - print self.data - - pydict = json.loads(self.data) # decode and add to queue - self.responseQueue.append(pydict) - - """ - do something to the dict - """ - - self.request.send("yes") # send back confirmation. - - def inputInit(self): - # initialize - self.host = "localhost" - self.port = 9999 - self.responseQueue = [] - # start server - self.server = SocketServer.TCPServer((self.host, self.port), InputTCPHandler) - self.server.responseQueue = self.responseQueue - self.server.serve_forever() # server keeps running till Ctrl+C or self.server.shutdown() is called. - - def sensingLoop(self): - # loop action handled through TCPHandler? - # if check says to shut down the server, shut it. - if self.doShutDown(): - self.server.shutdown() - else: - for event in self.responseQueue: - self.respond(event) - self.responseQueue = [] - - def doShutDown(self): - # do some checks to see if server should be shut down - return False; diff --git a/UDPInput.py b/UDPInput.py deleted file mode 100644 index f0af2b0..0000000 --- a/UDPInput.py +++ /dev/null @@ -1,16 +0,0 @@ -import Util, Input -import socket -class UDPInput(Input.Input): - def inputInit(self): - HOST = '' # Symbolic name meaning all available interfaces - PORT = self.argDict['Port'] # Arbitrary non-privileged port - self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self.sock.bind((HOST, PORT)) - print 'UDPINIT' - def sensingLoop(self): - print 'udploop' - (data,address) = self.sock.recvfrom(1024) - dataDict = {'data':data, 'address':address} - print 'LOLOLOLOL' - self.respond(dataDict) - diff --git a/Util.py b/Util.py index 7b98bca..cfea4f5 100644 --- a/Util.py +++ b/Util.py @@ -12,11 +12,22 @@ KINET_DEEPMAGIC = 0xc001d00d KINET_MAGICHASH = 0x69000420 KINET_PORTOUT = 0x0108 KINET_UNI = 0 +CONFIG_PATH = 'config/' kinetDict = {'flags': 0, 'startcode': 0, 'pad':0} 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 loadParamRequirementDict(className): + return fileToDict(CONFIG_PATH + className) +def loadConfigFile(fileName): + fileName = CONFIG_PATH + fileName + if '.params' in fileName: + return fileToDict(fileName) + if '.xml' in fileName: + config = ElementTree() + config.parse(fileName) + return config def fileToDict(fileName): fileText = '' with open(fileName) as f: @@ -25,7 +36,6 @@ def fileToDict(fileName): if fileText == '': return {} return eval(fileText) -print fileToDict('LayoutEngine.params') def combineColors(c1,c2): return [c1[i]+c2[i] for i in range(min(len(c1),len(c2)))] def multiplyColor(color, percent): @@ -110,7 +120,6 @@ def testXMLParse(fileName): config.parse(fileName) print generateArgDict(config.find('ChildElement')) print generateArgDict(config.find('Renderer')) -testXMLParse('TestXML.xml') ##CONSTANTS## location = 'Location' diff --git a/ZigzagLayout.py b/ZigzagLayout.py deleted file mode 100644 index 66d27ec..0000000 --- a/ZigzagLayout.py +++ /dev/null @@ -1,45 +0,0 @@ -from LayoutEngine import LayoutEngine -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(LayoutEngine): - def initLayout(self): - if not 'zigLength' in self.argDict: - raise Exception('zigLength must be defined in argDict') - if not 'zigAxis' in self.argDict: - raise Exception('zigAxis must be defined in argDict') - if not 'xDirection' in self.argDict: - self.argDict['xDirection'] = 1 #right - if not 'yDirection' in self.argDict: - self.argDict['yDirection'] = 1 #down - def layoutFunc(self, lastLocation): - if not 'buildQueue' in self.argDict: - self.argDict['buildQueue'] = self.argDict['zigLength'] - - newLoc = list(lastLocation) - if self.argDict['buildQueue'] > 1: - if self.argDict['zigAxis'] == 'X': - newLoc[0] += self.argDict['spacing'] * self.argDict['xDirection'] - else: - newLoc[1] += self.argDict['spacing'] * self.argDict['yDirection'] - self.argDict['buildQueue'] -= 1 - else: - self.argDict['buildQueue'] = self.argDict['zigLength'] - if self.argDict['zigAxis'] == 'X': - newLoc[1] += self.argDict['spacing'] * self.argDict['yDirection'] - else: - newLoc[0] += self.argDict['spacing'] * self.argDict['xDirection'] - if self.argDict['zigAxis'] == 'X': - self.argDict['xDirection'] *= -1 - else: - self.argDict['yDirection'] *= -1 - return newLoc - - diff --git a/behaviors/DebugBehavior.py b/behaviors/DebugBehavior.py new file mode 100644 index 0000000..4c8550a --- /dev/null +++ b/behaviors/DebugBehavior.py @@ -0,0 +1,8 @@ +from operationscore.Behavior import * +import Util +import pdb +class DebugBehavior(Behavior): + def processResponse(self, sensorInputs, recursiveInputs): + if sensorInputs != []: + print 'Sensor Inputs: ', sensorInputs + return [] diff --git a/behaviors/EchoBehavior.py b/behaviors/EchoBehavior.py new file mode 100644 index 0000000..a11558a --- /dev/null +++ b/behaviors/EchoBehavior.py @@ -0,0 +1,12 @@ +from operationscore.Behavior import * +import Util +import pdb +class EchoBehavior(Behavior): + def processResponse(self, sensorInputs, recursiveInputs): + ret = [] + for sensory in sensorInputs: + outDict = {} + outDict[Util.location] = sensory[Util.location] + outDict['Color'] = (255,0,0) + ret.append(outDict) + return ret diff --git a/behaviors/__init__.py b/behaviors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config/.LightInstallationConfig.xml.swp b/config/.LightInstallationConfig.xml.swp new file mode 100644 index 0000000..cfcec1b Binary files /dev/null and b/config/.LightInstallationConfig.xml.swp differ diff --git a/config/Behavior.params b/config/Behavior.params new file mode 100644 index 0000000..532fa03 --- /dev/null +++ b/config/Behavior.params @@ -0,0 +1 @@ +{'Inputs':'Inputs must be defined!'} diff --git a/config/DecayEvent.params b/config/DecayEvent.params new file mode 100644 index 0000000..67b7dcf --- /dev/null +++ b/config/DecayEvent.params @@ -0,0 +1,3 @@ +{'DecayType': 'Decay type missing. Specify Exponential or Proportional.', + 'Coefficient':'Coeffienct missing. Coefficient for decay type. E^(-ct) if + exponential, c/t if proportional.'} diff --git a/config/Input.params b/config/Input.params new file mode 100644 index 0000000..f051cb3 --- /dev/null +++ b/config/Input.params @@ -0,0 +1,2 @@ +{'InputId': 'InputId must be defined in config XML', 'RefreshInterval': +'RefreshInterval must be defined in config XML'} diff --git a/config/LayoutEngine.params b/config/LayoutEngine.params new file mode 100644 index 0000000..1b1dee5 --- /dev/null +++ b/config/LayoutEngine.params @@ -0,0 +1,6 @@ +{'pixelToPixelSpacing':'pixelToPixel spacing not defined in argDict. This is the +length of wire between 2 adjacent LEDs. Common values are 4 or 12. +Specified in config XML.', 'numPixels': 'numPixels not defined in +argDict. Common value: 50.', 'originLocation':'originLocation not +defined in argDict. Values should be a string in the form (x,y). This +should be specified in the config XML.'} diff --git a/config/LightInstallationConfig.xml b/config/LightInstallationConfig.xml new file mode 100644 index 0000000..a5f4a08 --- /dev/null +++ b/config/LightInstallationConfig.xml @@ -0,0 +1,94 @@ + + + + + + layouts.ZigzagLayout + + strip1 + 25 + X + -1 + 12 + 12 + 50 + (10,10) + + + + layouts.ZigzagLayout + + strip2 + 25 + X + 1 + 12 + 12 + 50 + (10,30) + + + + + + renderers.PygameRenderer + + (1300,50) + + + + renderers.IndoorRenderer + + + 10.1.218.72 + {'strip1':1, 'strip2':2} + + + + + + + inputs.PygameInput + + pygame + 100 + + + + inputs.UDPInput + + UDP + 6038 + 100 + + + + + + behaviors.EchoBehavior + + echo + 0 + + pygame + + + + + behaviors.DebugBehavior + + debug + 0 + + UDP + + + + + diff --git a/config/PixelEvent.params b/config/PixelEvent.params new file mode 100644 index 0000000..bc5dd82 --- /dev/null +++ b/config/PixelEvent.params @@ -0,0 +1 @@ +{'Color':'Color must be defined in argDict'} diff --git a/config/RendererConfig.xml b/config/RendererConfig.xml new file mode 100644 index 0000000..27fb98b --- /dev/null +++ b/config/RendererConfig.xml @@ -0,0 +1,14 @@ + + + + + IndoorRenderer + + + 10.1.218.72 + {'strip1':1} + + + + + diff --git a/config/StepEvent.params b/config/StepEvent.params new file mode 100644 index 0000000..64d4365 --- /dev/null +++ b/config/StepEvent.params @@ -0,0 +1 @@ +{'LightTime': 'You must define LightTime (ms) in argDict.'} diff --git a/config/TestXML.xml b/config/TestXML.xml new file mode 100644 index 0000000..20d7f6f --- /dev/null +++ b/config/TestXML.xml @@ -0,0 +1,23 @@ + + + + 1 + 2 + 3 + + normal + + + IndoorRenderer + + + 192.168.1.0 + {'strip1':1,'strip2':2} + + + 192.168.1.1 + {'strip3':1, 'strip4':2} + + + + diff --git a/config/UDPTestConfig.xml b/config/UDPTestConfig.xml new file mode 100644 index 0000000..8483833 --- /dev/null +++ b/config/UDPTestConfig.xml @@ -0,0 +1,24 @@ + + + + + UDPInput + + UDP + 6038 + + + + + + DebugBehavior + + debug + 0 + + UDP + + + + + diff --git a/docs/designDocs.pdf b/docs/designDocs.pdf new file mode 100644 index 0000000..78eb646 Binary files /dev/null and b/docs/designDocs.pdf differ diff --git a/docs/designDocs.tex b/docs/designDocs.tex new file mode 100644 index 0000000..2a9fd97 --- /dev/null +++ b/docs/designDocs.tex @@ -0,0 +1,178 @@ +\documentclass{article} +\usepackage{fullpage} +\begin{document} + \title{150 Smoots Lighting Installation Design Document} + \author{Russell Cohen} + \date{\today} + \maketitle + \newcommand{\classDoc}[5]{ + \subsection{#1} + \begin{itemize} + \item \textbf{Inherits from: } #2 + \item \textbf{Inherited by: } #3 + \item \textbf{Brief Description: } #4 + \item \textbf{Argument Requirements: } #5 + \end{itemize} + } + \section{Intro} + \textbf{NB: These docs give an overview of the classes and methods but + may lag behind the code for certain in-flux functionality. For + up-to-the minute docs, please use pydoc.} \\ + The system, which we will describe henceforth as SmootLight is a + modular system designed with the following goals in mind: + \begin{itemize} + \item The system must abstract away from all components while + remaining useful (\verb=Renderer=, \verb=Input=, \verb=Behavior=) + \item The system must be modular and be easy to write new code for. + \item More goals as I think of them + \end{itemize} + We accomplish this in the following manner: + \begin{itemize} + \item The system is configured by an XML file which specifies its + components. + \item All classes are initialized with a dictionary as an argument + containing anything a class may need. All objects are passed + between members as python dictionaries because their easy + serialization. + \end{itemize} + \section{Operations Class Patterns} + \classDoc{SmootCoreObject}{None}{All 2nd level classes (PixelAssembler, Renderer, + Input, Behavior)} + {SmootCoreObject is essentially a super-object + that makes things easy for us. It does the following actions: + \begin{itemize} + \item Defines a constructor that sets argDict + \item Defines a \texttt{\_\_getitem\_\_} , which lets us acces items in + argDict as if the class was a dictionary. + (\texttt{self['itemName']}) + \item Defines validateArgs and validateArgDict which + validate the incoming arguments against a dictionary + containing argument names as keys and an error message to + display if they are missing as a values. This file should + be named classname.params and look like a python dict + (\texttt{\{'key':value, 'key2':value2\}} ) + \end{itemize} + Note that at this point, the only class using this functionality + is the PixelEvent class.} + {No required parameters in argDict} + \classDoc{PixelAssembler}{SmootCoreObject}{LineLayout, ZigzagLayout}{ + PixelAssembler is a class that defines the positions of lights. It + provides a method \texttt{getLightLocations} which give a list of + all light locations for a given strip. Inheriting classes must + define \texttt{layoutFunc} which returns the next location given the + previous location. (They may simply override + \texttt{getLightLocations} + instead, if they wish, but be careful when doing so). In + heriting classes may defint \texttt{initLayout} which is called at + initialization.}{\begin{itemize} + \item \texttt{lightToLightSpacing}: this is the length of wire + between 2 adjacent LEDs. Common values are 4 or 12. + \item \texttt{numLights}: Number of lights in a strip. + \item \texttt{originLocation}: Location of the first light. + \end{itemize}} + \classDoc{Renderer}{SmootCoreObject}{PygameRenderer, IndoorRenderer}{ + Renderer is a class that serves as an abstract class for renderers + interacting with the system. Inheriting classes must define + render, which is passed a \texttt{lightSystem} object. Inheriting + classes may define initRenderer which is called on initiation. + }{No required arguments} + \classDoc{Input}{SmootCoreObject, threading.Thread}{PygameInput, + TCPInput,UDPInput}{Input is a abstract class which facilitates Inputs. + It does this by providing a method that is polled at a periodic + interval within which the inheriting class can raise an event. + Inheriting classes must define \texttt{sensingLoop} which is + called at the interval specified in the config. Inheriting + classes should call respond with an dictionary as an argument + to raise an event. Classes using (not + inheriting) input must pass a scope into + the argDict which offers a \texttt{processInput} + method. Inputs are marked as Daemon threads, and + are therefore killed when their parent is + killed.}{\begin{itemize} + \item \texttt{InputId}: The string id of a given input. Must be + unique. + \item Optional:\texttt{RefreshInterval}: The interval in + seconds (will soon be changed to milliseconds) between + sucessive calls to the sensingLoop method. TODO: make + timeout. + \end{itemize}} + \classDoc{Behavior}{SmootCoreObject}{EchoBehavior, DebugBehavior}{ +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 +spawned during the last time step. Inheriting classes MUST define +\texttt{processBehavior}. \texttt{processBehavior} should return a list of dictionaries which +define the properties of the light response. The must return a location +\texttt{PixelEvent} class. Soon be be deprecated:\textit{They must give a location and +color. They may define a function pointer which defines a custom mapping. +[More on this later. Bug Russell if you want to do it].} +Call \texttt{recursiveResponse} to queue a input on the next iteration with a dictionary +argument. This will be passed in via recursive inputs.} +{\begin{itemize} + \item \texttt{Inputs}: A list of input Ids specifying input to the + behavior. In the future, this may also contain behavior ids. +\end{itemize}} +\classDoc{PixelEvent}{SmootCoreObject}{StepResponse}{ + Abstract class defining the behavior of a light after it has been turned on. + Inheriting classes should defint \texttt{lightState} which is passed a + timeDelay in ms as an argument. \texttt{lightState} should return a + color or None if the response is complete.}{\begin{itemize} + \item \texttt{Color}: The color of response. \textit{This is may be + removed in the future} + \end{itemize} + } +\section{The Screen Class and Its Relatives} +\classDoc{Screen}{None}{None} +{The \texttt{Screen} class is a representation of an entire system of pixels, + distributed over space. The Screen class and its relatives process + responses (mapped to pixels via a LayoutEngine), and add PixelEvents + to the individual pixels. The Screen provides an instance that + renderers can call to determine the color of all the individual pixels. It contains a list of PixelStrips, which each + address the individual pixels. \texttt{Screen} offers a + \texttt{respond} method which takes a dictionary containing information + about a response. TODO: detail the contents of this dictionary (maybe + somewhere else). Note: \texttt{Screen} will not process its + responses until \texttt{timeStep} is called which processes all responses that + have been queued since the last time that \texttt{timeStep} was + called. Screen also offers an iterator over \textit{all} lights, + accesible by using an expression like: \texttt{for light in screen:}. For + addressing of specific \texttt{PixelStrips} , \texttt{self.pixelStrips} + is exposed.}{No required parameters} +\classDoc{PixelStrip}{None}{None} +{The \texttt{PixelStrip} class is a representation of a string of Pixels that are + connected in physical space (eg. a strip of lights). A \texttt{PixelStrip} takes a + \texttt{LayoutBuilder} (\textit{Name up for debate, currently known as layout + engine}) as an argument. The \texttt{argDict} of the + \texttt{LayoutBuilder} is + passed becomes the \texttt{argDict} of the \texttt{PixelStrip}. + \texttt{PixelStrip} generally shouldn't be + adressed directly unless you need Strip-Identification for rendering + purposes. You should never, for example, call \texttt{respond} on a + \texttt{PixelStrip} + directly, unless you really know what you're doing. Well, actually you + should never need to do that. + never. Don't do it.}{Takes a \texttt{LayoutBuilder} as an argument.} + \section{Best Practices} + \subsection{Variable and function naming} + I'm pretty bad about being consistent. However, in all future + code, please adhere to the following: + \begin{itemize} + \item Classes: \texttt{FirstLetterCaps} + \item Functions: \texttt{camelCase} + \item Property Names: \texttt{FirstLetterCaps} + \item Constants: \texttt{ALL\_CAPS\_WITH\_UNDERSCORES} + \end{itemize} + \subsection{Time} + For time, use the \texttt{Util.time()} method to return the current + time in ms. + \subsection{Acessing Pixels} + The ideal method for acessing Pixels in a screen is to use its + iterator. Iterating over the individual PixelStrips is also an + acceptable method. + \subsection{Determining the state of a \texttt{Pixel}} + The best practice for determining the color of a \texttt{Pixel} is to call + \texttt{lightState} (may be renamed to State TODO). This ensures + all current active responses running on the Pixel contribute correctly. + \subsection{Color} + For color, use a tuple of (R,G,B) 0-255 for each. Colors can be + easily manipulated with members of the Util class. + \end{document} diff --git a/inputs/PygameInput.py b/inputs/PygameInput.py new file mode 100644 index 0000000..6c84664 --- /dev/null +++ b/inputs/PygameInput.py @@ -0,0 +1,17 @@ +import time, Util +from operationscore.Input import * +import pygame +from pygame.locals import * +#This class processes input from an already running pygame instance and passes +#it to the parent. This class requires an already running pygame instance. +class PygameInput(Input): + def sensingLoop(self): + #try: + for event in pygame.event.get(): + if event.type is KEYDOWN: + self.respond({Util.location: (5,5),'Key': event.key}) + if event.type is MOUSEBUTTONDOWN: + self.respond({Util.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 new file mode 100644 index 0000000..72d8742 --- /dev/null +++ b/inputs/TCPInput.py @@ -0,0 +1,52 @@ +import SocketServer +import Util +from operationscore.Input import * + +""" +A rough sketch about how a TCP socket server receives data from the phone (or other stuff). +Some corrections are probably needed from Russell. +Looks good to me -- not really the way I envisioned it, but since the server +we're using has a built in loop. When we call the reponse method to pass the +data up the pipe, we should use the sensingLoop so that everything stays +thread-safe. +""" +class TCPInput(Input.Input): + class InputTCPHandler(SocketServer.BaseRequestHandler): + def handle(self): + # get data from the TCP socket connected to the client + self.data = self.request.recv(1024).strip() + print "%s wrote:" % self.client_address[0] + print self.data + + pydict = json.loads(self.data) # decode and add to queue + self.responseQueue.append(pydict) + + """ + do something to the dict + """ + + self.request.send("yes") # send back confirmation. + + def inputInit(self): + # initialize + self.host = "localhost" + self.port = 9999 + self.responseQueue = [] + # start server + self.server = SocketServer.TCPServer((self.host, self.port), InputTCPHandler) + self.server.responseQueue = self.responseQueue + self.server.serve_forever() # server keeps running till Ctrl+C or self.server.shutdown() is called. + + def sensingLoop(self): + # loop action handled through TCPHandler? + # if check says to shut down the server, shut it. + if self.doShutDown(): + self.server.shutdown() + else: + for event in self.responseQueue: + self.respond(event) + self.responseQueue = [] + + def doShutDown(self): + # do some checks to see if server should be shut down + return False; diff --git a/inputs/UDPInput.py b/inputs/UDPInput.py new file mode 100644 index 0000000..b0d6c93 --- /dev/null +++ b/inputs/UDPInput.py @@ -0,0 +1,17 @@ +import Util +from operationscore.Input import * +import socket +class UDPInput(Input): + def inputInit(self): + HOST = '' # Symbolic name meaning all available interfaces + PORT = self.argDict['Port'] # Arbitrary non-privileged port + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.sock.bind((HOST, PORT)) + print 'UDPINIT' + def sensingLoop(self): + print 'udploop' + (data,address) = self.sock.recvfrom(1024) + dataDict = {'data':data, 'address':address} + print 'LOLOLOLOL' + self.respond(dataDict) + diff --git a/layouts/LineLayout.py b/layouts/LineLayout.py new file mode 100644 index 0000000..3a8b747 --- /dev/null +++ b/layouts/LineLayout.py @@ -0,0 +1,5 @@ +from operationscore.LayoutEngine import * +#Simple layout class that simply makes a line of LEDs +class LineLayout(LayoutEngine): + def layoutFunc(self, lastLocation): + return (lastLocation[0]+self.argDict['spacing'], lastLocation[1]) diff --git a/layouts/ZigzagLayout.py b/layouts/ZigzagLayout.py new file mode 100644 index 0000000..26b27d8 --- /dev/null +++ b/layouts/ZigzagLayout.py @@ -0,0 +1,45 @@ +from operationscore.LayoutEngine 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(LayoutEngine): + def initLayout(self): + if not 'zigLength' in self.argDict: + raise Exception('zigLength must be defined in argDict') + if not 'zigAxis' in self.argDict: + raise Exception('zigAxis must be defined in argDict') + if not 'xDirection' in self.argDict: + self.argDict['xDirection'] = 1 #right + if not 'yDirection' in self.argDict: + self.argDict['yDirection'] = 1 #down + def layoutFunc(self, lastLocation): + if not 'buildQueue' in self.argDict: + self.argDict['buildQueue'] = self.argDict['zigLength'] + + newLoc = list(lastLocation) + if self.argDict['buildQueue'] > 1: + if self.argDict['zigAxis'] == 'X': + newLoc[0] += self.argDict['spacing'] * self.argDict['xDirection'] + else: + newLoc[1] += self.argDict['spacing'] * self.argDict['yDirection'] + self.argDict['buildQueue'] -= 1 + else: + self.argDict['buildQueue'] = self.argDict['zigLength'] + if self.argDict['zigAxis'] == 'X': + newLoc[1] += self.argDict['spacing'] * self.argDict['yDirection'] + else: + newLoc[0] += self.argDict['spacing'] * self.argDict['xDirection'] + if self.argDict['zigAxis'] == 'X': + self.argDict['xDirection'] *= -1 + else: + self.argDict['yDirection'] *= -1 + return newLoc + + diff --git a/layouts/__init__.py b/layouts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/operationscore/Behavior.py b/operationscore/Behavior.py new file mode 100644 index 0000000..f29430f --- /dev/null +++ b/operationscore/Behavior.py @@ -0,0 +1,33 @@ +#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 +#spawned during the last time step. Inheriting classes MUST define +#processResponse. processResponse should return a list of dictionaries which +#define the properties of the light response. They must give a location and +#color. They may define a function pointer which defines a custom mapping. +#[More on this later. Bug Russell if you want to do it]. +#recursiveResponse to queue a input on the next iteration with a dictionary +#argument. This will be passed in via recursive inputs. +import pdb +from operationscore.SmootCoreObject import * +#timeStep is called on every iteration of the LightInstallation +#addInput is called on each individual input received, and the inputs queue +class Behavior(SmootCoreObject): + def init(self): + self.validateArgs('Behavior.params') + if type(self['Inputs']) != type([]): + self['Inputs'] = [self['Inputs']] + self.recursiveResponseQueue = [] + self.sensorResponseQueue = [] + self.outGoingQueue = [] + def processResponse(self, sensorInputs, recursiveInputs): + pass + def addInput(self, sensorInputs): + self.sensorResponseQueue.append(sensorInputs) + def recursiveReponse(self, args): + self.responseQueue.append(args) + def timeStep(self): + responses = self.processResponse(self.sensorResponseQueue, \ + self.recursiveResponseQueue) + self.sensorResponseQueue = [] + self.recursiveResponseQueue = [] + return responses diff --git a/operationscore/Input.py b/operationscore/Input.py new file mode 100644 index 0000000..1ba4528 --- /dev/null +++ b/operationscore/Input.py @@ -0,0 +1,46 @@ +import threading,time,Util +#Abstract class for inputs. Inheriting classes should call "respond" to raise +#their event. Inheriting classes MUST define sensingLoop. Called at the +#interval specified in RefreshInterval while the input is active. For example, if you are writing +#webserver, this is where the loop should go. +#Inheriting classes MAY define inputInit. This is called before the loop +#begins. +import pdb +class Input(threading.Thread): + #Event scope is a function pointer the function that will get called when + #an Parent is raised. + def __init__(self, argDict): + self.eventQueue = [] + self.parentScope = argDict['parentScope'] + self.argDict = argDict + if not 'InputId' in argDict: + raise Exception('InputId must be defined in config xml') + if not 'RefreshInterval' in argDict: + print 'RefreshInterval not defined. Defaulting to .5s.' + self.argDict['RefreshInterval'] = 500 + self.inputInit() + threading.Thread.__init__(self) + self.daemon = True #This kills this thread when the main thread stops + def respond(self, eventDict): + #if eventDict != []: + #pdb.set_trace() + self.parentScope.processResponse(self.argDict, eventDict) + 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() + return parentAlive + except: + return False + def run(self): + while self.parentAlive(): + time.sleep(self.argDict['RefreshInterval']/float(1000)) + self.sensingLoop() + def sensingLoop(self): + pass + def inputInit(self): + pass + + diff --git a/operationscore/LayoutEngine.py b/operationscore/LayoutEngine.py new file mode 100644 index 0000000..700b554 --- /dev/null +++ b/operationscore/LayoutEngine.py @@ -0,0 +1,30 @@ +from operationscore.SmootCoreObject import * +import Util +import pdb +class LayoutEngine(SmootCoreObject): + def init(self): + self.validateArgs('LayoutEngine.params') + self.initLayout() + def layoutFunc(self, lastLocation): #Must be defined by inheriting class. + #Returns tuple pair (x,y) + pass + def getPixelLocations(self): #returns a complete list of locations of Pixels + #for a strip + locations = [self.argDict['originLocation']] + for pixelIndex in range(self['numPixels']-1): #-1 because origin + #already exists + newLocation = self.layoutFunc(locations[-1]) + if newLocation == None: + raise Exception('Location cannot be null. layoutFunc not \ + defined or improperly defined.') + if Util.dist(newLocation, locations[-1]) > \ + self['pixelToPixelSpacing']: + raise Exception('Illegal pixel location. Distance \ + between adjacent pixels must be less than \ + pixelToPixelSpacing.') + locations.append(newLocation) + return locations + def initLayout(self): + pass + def getStripArgs(self): #TODO: triage and remove + return self.argDict diff --git a/operationscore/PixelEvent.py b/operationscore/PixelEvent.py new file mode 100644 index 0000000..07669cd --- /dev/null +++ b/operationscore/PixelEvent.py @@ -0,0 +1,13 @@ +#Class defining a light response. Inheriting classes should define lightState, +#which should return a color, or None if the response is complete. Consider +#requiring a generate event. +from operationscore.SmootCoreObject import * +class PixelEvent(SmootCoreObject): + def init(self): + self.validateArgs('PixelEvent.params') + self.initEvent() + def initEvent(self): + pass + def state(self,timeDelay): + pass + diff --git a/operationscore/Renderer.py b/operationscore/Renderer.py new file mode 100644 index 0000000..11fd8ca --- /dev/null +++ b/operationscore/Renderer.py @@ -0,0 +1,12 @@ +#Renderer abstract class. Doesn't do much now, but might do more later. +#Inheriting classes MUST define render which takes a light system and renders it. +#Inheriting classes may define initRenderer which is called after the dictionary +#is pulled from config. +from operationscore.SmootCoreObject import * +class Renderer(SmootCoreObject): + def init(self): + self.initRenderer() + def render(lightSystem): + pass + def initRenderer(self): + pass diff --git a/operationscore/SmootCoreObject.py b/operationscore/SmootCoreObject.py new file mode 100644 index 0000000..2901ef6 --- /dev/null +++ b/operationscore/SmootCoreObject.py @@ -0,0 +1,23 @@ +import Util +import pdb +class SmootCoreObject: + def __init__(self, argDict): + self.argDict = argDict + self.init() #call init of inheriting class + def init(self): + pass + def __setitem__(self,k, item): + self.argDict[k] = item + def __getitem__(self, item): + if item in self.argDict: + return self.argDict[item] + else: + return None + def __getiter__(self): + return self.argDict.__getiter__() + def validateArgs(self, argFileName): + self.validateArgDict(Util.loadParamRequirementDict(argFileName)) + def validateArgDict(self, validationDict): + for item in validationDict: + if not item in self.argDict: + raise Exception(validationDict[item]) diff --git a/operationscore/__init__.py b/operationscore/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pixelcore/Pixel.py b/pixelcore/Pixel.py new file mode 100644 index 0000000..6784c63 --- /dev/null +++ b/pixelcore/Pixel.py @@ -0,0 +1,44 @@ +import Util +import pdb +from pixelevents.StepEvent import * +#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 cue. If a member returns none, +#it is removed from the queue. Otherwise, its value added to the Pixels color +#weighted by z-index. +class Pixel: + radius = 2 + timeOff = -1 + def __init__(self, location, color): + self.location = location + self.color = color + self.events = {} + def turnOn(self): + self.turnOnFor(-1) + def turnOnFor(self, time): + event = StepEvent.generate(time, (255,0,255)) #TODO: Move color to + self.processInput(event, 0) + #arg + def processInput(self,pixelEvent,zindex): #consider migrating arg to dict + self.events[Util.time()] = (zindex, pixelEvent) + def clearAllEvents(self): + self.events = {} + def state(self): + deadEvents = [] + currentTime = Util.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) + print resultingColor + else: + deadEvents.append(eventTime) + [self.events.pop(event) for event in deadEvents] + if sum(resultingColor) > 0: + print resultingColor + return tuple(resultingColor) + def __str__(self): + return 'Loc: ' + str(self.location) + diff --git a/pixelcore/PixelStrip.py b/pixelcore/PixelStrip.py new file mode 100644 index 0000000..14c87d9 --- /dev/null +++ b/pixelcore/PixelStrip.py @@ -0,0 +1,40 @@ +from pixelcore.Pixel import * +from pixelevents.StepEvent import * +import pygame +import math +import Util +import pdb +#Python class representing a single Pixel strip (usually 50 Pixels) +class PixelStrip: + def __init__(self, layoutEngine): + self.initStrip(layoutEngine) + self.argDict = layoutEngine.getStripArgs() + def initStrip(self, layoutEngine): + pixelLocations = layoutEngine.getPixelLocations() + self.pixels = [Pixel(l, (0,0,0)) for l in pixelLocations] + def __iter__(self): + return self.pixels.__iter__() + def render(self, surface): + [l.render(surface) for l in self.pixels] + #step + def allOn(self, time): + [l.turnOnFor(time) for l in self.pixels] #TODO: add test-on method to + #pixels + def respond(self, responseInfo): + print 'PixelEvent', responseInfo + location = responseInfo[Util.location] + if not 'PixelEvent' in responseInfo: + if 'Color' in responseInfo: + color = responseInfo['Color'] + else: + raise Exception('Need Color. Probably') + responseInfo['PixelEvent'] = StepEvent.generate(300, color) + (dist, pixel) = self.getPixelNearest(location) + 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.sort() + return dists[0] + #just for now. + diff --git a/pixelcore/Screen.py b/pixelcore/Screen.py new file mode 100644 index 0000000..9806daa --- /dev/null +++ b/pixelcore/Screen.py @@ -0,0 +1,31 @@ +from pixelcore.Pixel import * +from pixelcore.PixelStrip import * +import itertools +class Screen: + def __init__(self): + self.responseQueue = [] + self.pixelStrips = [] + def addStrip(self, lS): + self.pixelStrips.append(lS) + def render(self, surface): + [lS.render(surface) for lS in self.pixelStrips] + def allOn(self): + [lS.allOn(-1) for lS in self.pixelStrips] + def __iter__(self): #the iterator of all our pixel strips chained togther + return itertools.chain(*[strip.__iter__() for strip in self.pixelStrips]) + #increment time -- This processes all queued responses. Responses generated + #during this period are added to the queue that will be processed on the next + #time step. + def timeStep(self): + tempQueue = list(self.responseQueue) + self.responseQueue = [] + for response in tempQueue: + self.processResponse(response) + #public + def respond(self, responseInfo): + self.responseQueue.append(responseInfo) + #private + def processResponse(self, responseInfo): #we need to make a new dict for + #each to prevent interference + [strip.respond(dict(responseInfo)) for strip in self.pixelStrips] + diff --git a/pixelcore/__init__.py b/pixelcore/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pixelevents/DecayEvent.py b/pixelevents/DecayEvent.py new file mode 100644 index 0000000..c9fc226 --- /dev/null +++ b/pixelevents/DecayEvent.py @@ -0,0 +1,12 @@ +from pixelcore import PixelEvent +import Util, math +class DecayEvent(PixelEvent): + def initEvent(self): + self.validateArgs('DecayEvent.params') + self['Coefficient'] = abs(self['Coefficient']) + def state(self,timeDelay): + if self['DecayType'] == 'Exponential': + decay = math.exp(timeDelay*-1*self['Coefficient']) + if self['DecayType'] == 'Proportional': + decay = float(self['Coefficient']) / timeDelay + return Util.multiplyColor(self['Color'], decay) diff --git a/pixelevents/StepEvent.py b/pixelevents/StepEvent.py new file mode 100644 index 0000000..d95271e --- /dev/null +++ b/pixelevents/StepEvent.py @@ -0,0 +1,14 @@ +from operationscore.PixelEvent import * +class StepEvent(PixelEvent): + def initEvent(self): + self.validateArgs('StepEvent.params') + def state(self,timeDelay): + if timeDelay < self['LightTime'] or self['LightTime'] == -1: + return self['Color'] + else: + return None + @staticmethod + def generate(onTime, color): + args = {'LightTime': onTime, 'Color': color} + return StepEvent(args) + diff --git a/pixelevents/__init__.py b/pixelevents/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/renderers/IndoorRenderer.py b/renderers/IndoorRenderer.py new file mode 100644 index 0000000..efe2b3a --- /dev/null +++ b/renderers/IndoorRenderer.py @@ -0,0 +1,31 @@ +from operationscore.Renderer import * +import socket, Util +import pdb +kinetPort = 6038 +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 + #a strip +# g self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + powerSupplies = self.argDict['PowerSupply'] + if not type(powerSupplies) == type([]): + powerSupplies = [powerSupplies] + for powerSupply in powerSupplies: + ip = powerSupply['IP'] + stripsInPowerSupply = powerSupply['PortMapping'] + for stripId in stripsInPowerSupply: + 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) + diff --git a/renderers/PygameRenderer.py b/renderers/PygameRenderer.py new file mode 100644 index 0000000..6f7f65b --- /dev/null +++ b/renderers/PygameRenderer.py @@ -0,0 +1,20 @@ +from operationscore.Renderer import * +import pygame +from pygame.locals import * +import pdb +class PygameRenderer(Renderer): + def initRenderer(self): + pygame.init() + self.screen = pygame.display.set_mode((1300,50)) + self.background = pygame.Surface(self.screen.get_size()) + self.background = self.background.convert() + self.background.fill(Color('Black')) + def render(self, lightSystem): + self.background.fill(Color('Black')) + #print 'drawing color:',light.color + for light in lightSystem: + pygame.draw.circle(self.background, light.state(), light.location, \ + light.radius) + + self.screen.blit(self.background, (0,0)) + pygame.display.flip() diff --git a/renderers/__init__.py b/renderers/__init__.py new file mode 100644 index 0000000..e69de29 -- cgit v1.2.3