# import offEffect from effects.offEffect import offEffect from rgbUtils.debug import debug from rgbUtils.BaseEffect import BaseEffect from rgbUtils.RGBStrip import RGBStrip import time import threading import os import sys # # handles all the effects: # - start/stop effects # - changes parameters of specific effects # - moves the rgbStrips to the effects # - runs a guardian that detects dead effectThreads and moves Strips to offEffect # class effectController: # list of current running effects effectThreads = [] offEffectThreadObject = None # maybe i will seperate this later onControllerChangeHandler = [] def __init__(self,rgbStripController): self.rgbStripController = rgbStripController # load the effects self.effectsList = self.getEffectsListFromDir() # start the offEffect by default self.offEffectThreadObject = self.startEffect(offEffect,[]) # - a bit of failover handling, remove dead threads from effectThread array # - move strips without an effect to the offEffect self.effectGuardian = self.effectGuardian(self) self.effectGuardian.start() # starts a effect by given class, rgbStrips and params ([[index,[param,param,param]],[index,[param,param,param]]]) def startEffect(self, effectClass: BaseEffect, rgbStrips: list, params: list = []): newEffect = effectClass() if len(self.effectThreads) > 0 and len(rgbStrips) > 0 or len(self.effectThreads) == 0: newEffect.start() self.updateEffectParameters(newEffect, params) self.effectThreads.append(newEffect) for rgbStrip in rgbStrips: self.moveRGBStripToEffectThread(rgbStrip, newEffect) self.noticeControllerChange() return newEffect # returns all effectClasses but offEffect, since offEffect will never be killed # and should not be running twice def getEffects(self): # alle außer offEffect return self.effectsList # returns all running effectThreads def getEffectThreads(self): return self.effectThreads # returns a list of the RGBStrips used by this effect def getEffectRGBStrips(self, effectThreadObject: BaseEffect): return effectThreadObject.effectRGBStrips() # move a rgbStip to a running effectThread def moveRGBStripToEffectThread(self, rgbStrip: RGBStrip, effectThreadObject: BaseEffect): # cycle throught all effects and # remove Strip from effect if added for et in self.effectThreads: if rgbStrip in et.effectRGBStrips(): et.removeRGBStrip(rgbStrip) if effectThreadObject.isAlive(): effectThreadObject.addRGBStrip(rgbStrip) # check if any effectThread has no more rgbStrips and if so, stop it # if the effectThread has no more strips, we stop it and remove it. for x, effectThread in enumerate(self.effectThreads): if len(effectThread.effectRGBStrips()) == 0 and x is not 0: effectThread.stopEffect() self.effectThreads.remove(effectThread) self.noticeControllerChange() # updates parameter of a running effectThread def updateEffectParameters(self, effectThreadObject: BaseEffect, effectParameters): for effectParameter in effectParameters: effectThreadObject.updateEffectParameterValues( effectParameter[0], effectParameter[1]) self.noticeControllerChange() # stops all effectThreads and set the rgbStrips to off def stopAll(self): debug("effectController stopAll()") for effectThread in self.effectThreads: effectThread.stopEffect() debug("effectController killed "+str(effectThread)) self.effectGuardian.stop() time.sleep(0.5) # GPIO.cleanup() # inform the controllerChangeHandler to update the client def noticeControllerChange(self): for controllerChangeHandler in self.onControllerChangeHandler: controllerChangeHandler() # add onControllerChangeHandler def addOnControllerChangeHandler(self, hander): print("addOnControllerChangeHandler", str(hander)) self.onControllerChangeHandler.append(hander) # send data to this client hander() # remove onControllerChangeHandler def removeOnControllerChangeHandler(self, hander): print("removeOnControllerChangeHandler", str(hander)) if hander in self.onControllerChangeHandler: self.onControllerChangeHandler.remove(hander) else: print('\n\n -> client was never registered!') # automaticly loads all modules from effects subdir and adds them to the list of effects if they have the BaseEffect as subclass def getEffectsListFromDir(self): effectsList = [] for raw_module in os.listdir(os.path.abspath(os.path.join(os.path.join(os.path.dirname(__file__), os.pardir),"effects"))): if raw_module == '__init__.py' or raw_module == 'offEffect.py' or raw_module[-3:] != '.py': continue effectModule = __import__("effects."+raw_module[:-3], fromlist=[raw_module[:-3]]) effectClass = getattr(effectModule, raw_module[:-3]) if issubclass(effectClass,BaseEffect): effectsList.append(effectClass) print("Loaded effects: ",effectsList) return effectsList def onRGBStripRegistered(self,rgbStrip): self.offEffectThreadObject.addRGBStrip(rgbStrip) self.noticeControllerChange() def onRGBStripUnRegistered(self,rgbStrip): # cycle throught all effects and # remove Strip from effect if added for et in self.effectThreads: if rgbStrip in et.effectRGBStrips(): et.removeRGBStrip(rgbStrip) # if the effectThread has no more strips, we stop it and remove it. for x, effectThread in enumerate(self.effectThreads): if len(effectThread.effectRGBStrips()) == 0 and x is not 0: effectThread.stopEffect() self.effectThreads.remove(effectThread) self.noticeControllerChange() class effectGuardian(threading.Thread): def __init__(self, effectController): threading.Thread.__init__(self) self.effectController = effectController self.stopped = False def run(self): while not self.stopped: for effectThread in self.effectController.effectThreads: # if Thread was killed by something else, we remove it from the list if not effectThread.isAlive(): for rgbStrip in effectThread.effectRGBStrips(): self.effectController.moveRGBStripToEffectThread(rgbStrip,self.effectController.offEffectThreadObject) self.effectController.effectThreads.remove(effectThread) print("effectController:effectGuardian removed dead Thread " + str(effectThread) + ". There must be an error in code!") self.effectController.noticeControllerChange() time.sleep(1) def stop(self): self.stopped = True