From 798775805c9c589de6a5fd827fae0739645175f5 Mon Sep 17 00:00:00 2001 From: dezeyer Date: Sat, 11 May 2019 14:28:42 +0000 Subject: [PATCH] renaming folders and files to python format --- .../Helper}/effectControllerJsonHelper.py | 0 .../Helper}/rgbStripControllerJsonHelper.py | 0 server/BackendProvider/UDPSocketProvider.py | 11 +- server/BackendProvider/WebSocketProvider.py | 33 +-- .../MusikEffect.py} | 45 ++-- server/Effects/MusikEffect2.py | 184 +++++++++++++ .../offEffect.py => Effects/OffEffect.py} | 5 +- .../onEffect.py => Effects/OnEffect.py} | 7 +- .../RainbowEffect.py} | 6 +- .../StrobeEffect.py} | 5 +- server/{effects => Effects}/__init__.py | 0 server/{rgbUtils => Utils}/BaseEffect.py | 21 +- server/Utils/EffectController.py | 245 ++++++++++++++++++ server/{rgbUtils => Utils}/EffectParameter.py | 8 +- .../PyAudioRecorder.py} | 3 +- server/{rgbUtils => Utils}/RGBStrip.py | 10 +- .../RGBStripController.py} | 11 +- server/{rgbUtils => Utils}/__init__.py | 0 server/rgbUtils/effectController.py | 183 ------------- 19 files changed, 509 insertions(+), 268 deletions(-) rename server/{rgbUtils => BackendProvider/Helper}/effectControllerJsonHelper.py (100%) rename server/{rgbUtils => BackendProvider/Helper}/rgbStripControllerJsonHelper.py (100%) rename server/{effects/musikEffect.py.out => Effects/MusikEffect.py} (85%) create mode 100644 server/Effects/MusikEffect2.py rename server/{effects/offEffect.py => Effects/OffEffect.py} (82%) rename server/{effects/onEffect.py => Effects/OnEffect.py} (92%) rename server/{effects/rainbowEffect.py => Effects/RainbowEffect.py} (97%) rename server/{effects/strobeEffect.py => Effects/StrobeEffect.py} (91%) rename server/{effects => Effects}/__init__.py (100%) rename server/{rgbUtils => Utils}/BaseEffect.py (86%) create mode 100644 server/Utils/EffectController.py rename server/{rgbUtils => Utils}/EffectParameter.py (89%) rename server/{rgbUtils/pyAudioRecorder.py => Utils/PyAudioRecorder.py} (99%) rename server/{rgbUtils => Utils}/RGBStrip.py (90%) rename server/{rgbUtils/rgbStripController.py => Utils/RGBStripController.py} (85%) rename server/{rgbUtils => Utils}/__init__.py (100%) delete mode 100644 server/rgbUtils/effectController.py diff --git a/server/rgbUtils/effectControllerJsonHelper.py b/server/BackendProvider/Helper/effectControllerJsonHelper.py similarity index 100% rename from server/rgbUtils/effectControllerJsonHelper.py rename to server/BackendProvider/Helper/effectControllerJsonHelper.py diff --git a/server/rgbUtils/rgbStripControllerJsonHelper.py b/server/BackendProvider/Helper/rgbStripControllerJsonHelper.py similarity index 100% rename from server/rgbUtils/rgbStripControllerJsonHelper.py rename to server/BackendProvider/Helper/rgbStripControllerJsonHelper.py diff --git a/server/BackendProvider/UDPSocketProvider.py b/server/BackendProvider/UDPSocketProvider.py index 9b58df2..7089357 100644 --- a/server/BackendProvider/UDPSocketProvider.py +++ b/server/BackendProvider/UDPSocketProvider.py @@ -18,7 +18,6 @@ class ThreadedUDPServer(threading.Thread): self.start() self.udpClientGuardian = self.UDPClientGuardian() self.udpClientGuardian.start() - UDPClients def run(self): @@ -86,13 +85,6 @@ class UDPClient(): self.lastping = time() def handle(self, request): - - clientdata = request[0].decode() - self.socket = request[1] - #print(time(),"{} wrote:".format(self.client_address)) - #print(time(),"clientdata -> ", clientdata) - # socket.sendto(bytes("pong","utf-8"),self.client_address) - # Client Types: # CLIENT_TYPE_CONTROLLER = 0 # CLIENT_TYPE_STRIPE = 1 @@ -104,8 +96,9 @@ class UDPClient(): # s:ping # UDP + self.socket = request[1] try: - data = clientdata.split(':') + data = request[0].decode().split(':') #print(data) # r:1:srg strip name if data[0] == "r" and int(data[1]) == CLIENT_TYPE_STRIPE and data[2] != None and self.registered is False: diff --git a/server/BackendProvider/WebSocketProvider.py b/server/BackendProvider/WebSocketProvider.py index e894d4e..5475552 100644 --- a/server/BackendProvider/WebSocketProvider.py +++ b/server/BackendProvider/WebSocketProvider.py @@ -22,8 +22,8 @@ class ThreadedWebSocketServer(Thread): import json -from rgbUtils import effectControllerJsonHelper -from rgbUtils import rgbStripControllerJsonHelper +from BackendProvider.Helper import effectControllerJsonHelper +from BackendProvider.Helper import rgbStripControllerJsonHelper import traceback import logging @@ -63,12 +63,6 @@ class HTTPWebSocketsHandler(WebSocket): ledcount=1 if "led_count" in data: ledcount = int(data["led_count"]) - self.nojson=0 - if "nojson" in data: - self.nojson = int(data["nojson"]) - self.nosend=0 - if "nosend" in data: - self.nosend = int(data["nosend"]) # registers the strip with websocket object and name. the onRGBStripValueUpdate(rgbStrip) is called by # by the rgbStrip when an effectThread updates it @@ -87,13 +81,9 @@ class HTTPWebSocketsHandler(WebSocket): }) ) return + # the stripe should usualy not send any data, i do not know why it should... elif self.client_type is CLIENT_TYPE_STRIPE: - if self.nosend == 1: - respdata = "d" - for i in range(self.rgbStrip.STRIP_LENGHT): - respdata += ":" + str(i) + ":"+str(self.rgbStrip.red[i])+":"+str(self.rgbStrip.green[i])+":"+str(self.rgbStrip.blue[i]) - self.sendMessage(respdata) return # audio recorder responses are handled by the effectControllerJsonHandler elif self.client_type is CLIENT_TYPE_RECORDER: @@ -139,15 +129,8 @@ class HTTPWebSocketsHandler(WebSocket): # when a rgbStrip value is changed, send json data to client def onRGBStripValueUpdate(self,rgbStrip): - if self.nosend == 0: - if self.nojson == 0: - self.sendMessage( - json.dumps({ - 'data': rgbStripControllerJsonHelper.getRGBData(rgbStrip) - }) - ) - if self.nojson == 1: - respdata = "d" - for i in range(rgbStrip.STRIP_LENGHT): - respdata += ":" + str(i) + ":"+str(rgbStrip.red[i])+":"+str(rgbStrip.green[i])+":"+str(rgbStrip.blue[i]) - self.sendMessage(respdata) + self.sendMessage( + json.dumps({ + 'data': rgbStripControllerJsonHelper.getRGBData(rgbStrip) + }) + ) diff --git a/server/effects/musikEffect.py.out b/server/Effects/MusikEffect.py similarity index 85% rename from server/effects/musikEffect.py.out rename to server/Effects/MusikEffect.py index e2b6c3d..2b7ff68 100644 --- a/server/effects/musikEffect.py.out +++ b/server/Effects/MusikEffect.py @@ -1,15 +1,13 @@ -from rgbUtils.BaseEffect import BaseEffect -from rgbUtils.EffectParameter import slider, colorpicker -from rgbUtils.debug import debug +from Utils.BaseEffect import BaseEffect +from Utils.EffectParameter import slider, colorpicker import time import sys import numpy -from rgbUtils.pyAudioRecorder import pyAudioRecorder from time import perf_counter, sleep from random import randint, shuffle -class musikEffect(BaseEffect): +class MusikEffect(BaseEffect): name = "musikEffect" desc = "LED-Band *sollte* nach musik blinken" @@ -18,7 +16,7 @@ class musikEffect(BaseEffect): # Something that will be used to show descriptions and value options # of the parameters the effect will accept, in a way, that eg the webclient can decide, # if the parameters can be toggeled by a button/checkbox/slider/whatever - effectParameters = [ + effectParameters: list = [ # radio(\ # "Shuffle LED to Freq Order",\ # "Off -> Mapping ",\ @@ -42,12 +40,14 @@ class musikEffect(BaseEffect): self.fft_random_keys = [0,1,2,3] self.fft_random = [0,0,0,0] + self.y_max_freq_avg_list = [] + self.low_freq_avg_list = [] + self.bpm_list = [] + self.prev_beat = 0 # timestamp + # used by strobe() self.lastmode = 0 - self.recorderClient = pyAudioRecorder.recorderClient() - self.rgbStripController.pyAudioRecorder.registerRecorderClient(self.recorderClient) - return #loop effect as long as not stopped @@ -67,17 +67,17 @@ class musikEffect(BaseEffect): # b=0 # RGBStrip.RGB(r,g,b) #self.lastTime = time.time() - sleep(.001) + sleep(0.00833333333) def end(self): - self.rgbStripController.pyAudioRecorder.unregisterRecorderClient(self.recorderClient) + pass def plot_audio_and_detect_beats(self): - if not self.rgbStripController.pyAudioRecorder.has_new_audio: + if not self.hasNewFttValues: return # get x and y values from FFT - xs, ys = self.rgbStripController.pyAudioRecorder.fft() + xs, ys = self.ftt # calculate average for all frequency ranges y_avg = numpy.mean(ys) @@ -88,9 +88,9 @@ class musikEffect(BaseEffect): #mid_freq = numpy.mean(ys[88:115]) #hig_freq = numpy.mean(ys[156:184]) - low_freq = numpy.mean(ys[0:67]) - mid_freq = numpy.mean(ys[68:135]) - hig_freq = numpy.mean(ys[136:204]) + low_freq = numpy.mean(ys[0:int(ys.__len__() / 3 * 1)]) + mid_freq = numpy.mean(ys[int(ys.__len__() / 3 * 1):int(ys.__len__() / 3 * 2)]) + hig_freq = numpy.mean(ys[int(ys.__len__() / 3 * 2):int(ys.__len__() / 3 * 3)]) #get the maximum of all freq if len(self.y_max_freq_avg_list) < 250 or y_avg > 10 and numpy.amax([low_freq,mid_freq,hig_freq])/2 > numpy.amin(self.y_max_freq_avg_list): @@ -166,6 +166,16 @@ class musikEffect(BaseEffect): self.fft_random[1], self.fft_random[2] ) + """ + rgbStrip: RGBStrip + for rgbStrip in self.effectRGBStrips(): + for i in range(rgbStrip.STRIP_LENGHT): + if rgbStrip.STRIP_LENGHT-1-10 is not i: + rgbStrip.WS2812b(rgbStrip.STRIP_LENGHT-i-1, rgbStrip.red[rgbStrip.STRIP_LENGHT-i-2], rgbStrip.green[rgbStrip.STRIP_LENGHT-i-2], rgbStrip.blue[rgbStrip.STRIP_LENGHT-i-2]) + else: + rgbStrip.WS2812b(rgbStrip.STRIP_LENGHT-i-1, self.fft_random[0], self.fft_random[1], self.fft_random[2]) + rgbStrip.show() + """ # shorten the cumulative list to account for changes in dynamics if len(self.low_freq_avg_list) > 500: @@ -187,8 +197,7 @@ class musikEffect(BaseEffect): self.low_freq_avg_list = [] print("new song") self.off() - - self.rgbStripController.pyAudioRecorder.newAudio = False + self.hasNewFttValues = False # print(self.bpm_list)250 def strobe(self): diff --git a/server/Effects/MusikEffect2.py b/server/Effects/MusikEffect2.py new file mode 100644 index 0000000..be23782 --- /dev/null +++ b/server/Effects/MusikEffect2.py @@ -0,0 +1,184 @@ +from Utils.BaseEffect import BaseEffect +from Utils.RGBStrip import RGBStrip +from Utils.EffectParameter import slider, colorpicker +import time + +import sys +import numpy +from time import perf_counter, sleep +from random import randint, shuffle + +import typing + +import matplotlib +import matplotlib.pyplot as plt +#matplotlib.use('TkAgg') # THIS MAKES IT FAST! +matplotlib.get_backend() + +class MusikEffect2(BaseEffect): + name = "musikEffect2" + desc = "LED-Band *sollte* nach musik blinken" + + + + # Something that will be used to show descriptions and value options + # of the parameters the effect will accept, in a way, that eg the webclient can decide, + # if the parameters can be toggeled by a button/checkbox/slider/whatever + """radio( + "Shuffle LED to Freq Order", + "Off -> Mapping ", + [ + [0,255,255,"red"], + [0,255,255,"green"], + [0,255,255,"blue"] + ] + ),""" + effectParameters: list = [ + slider( + "Effect Brightnes", + "Choose a brightness for your LED's", + [ + [0,100,100,"brightness"], + ] + ) + ] + + def init(self): + + + self.fft_random_keys = [0,1,2,3] + self.fft_random = [0,0,0,0] + + self.y_max_freq_avg_list = [0] + self.low_freq_avg_list = [0] + self.bpm_list = [] + self.prev_beat = 0 # timestamp + + ''' + self.fig = plt.gcf() + + self.fig.show() + ''' + return + + + #loop effect as long as not stopped + def effect(self): + '''plt.clf() + #plt.ylim(-.5, numpy.amax(self.y_max_freq_avg_list)) + plt.xlabel('xs') + plt.ylabel('ys') + plt.plot(self.ftt[0],self.ftt[1]) + self.fig.canvas.draw() + self.fig.canvas.flush_events()''' + self.plot_audio_and_detect_beats() + #self.freqtocolor() + #if(time.time() - self.lastTime >= 0.002): + #for RGBStrip in self.effectRGBStrips(): + # r= RGBStrip.red-1 + # if(r<0): + # r=0 + # g= RGBStrip.green-1 + # if(g<0): + # g=0 + # b= RGBStrip.blue-1 + # if(b<0): + # b=0 + # RGBStrip.RGB(r,g,b) + #self.lastTime = time.time() + sleep(0.00833333333) + + def end(self): + pass + + def plot_audio_and_detect_beats(self): + if not self.hasNewFttValues: + return + + # get x and y values from FFT + xs, ys = self.ftt + + amax = numpy.amax(ys) + if amax > 0: + self.y_max_freq_avg_list.append(amax) + if len(self.y_max_freq_avg_list ) > 50000: + self.y_max_freq_avg_list = self.y_max_freq_avg_list[25000:] + + calc = numpy.amax(ys) / numpy.amax(self.y_max_freq_avg_list) * 765 + """print(amax) + print(numpy.amax(self.y_max_freq_avg_list)) + print(calc) + print("-----------") + """ + c: self.Color = self.pitchRainbow(calc) + rgbStrip: RGBStrip + for rgbStrip in self.effectRGBStrips(): + """ + for i in range(rgbStrip.STRIP_LENGHT): + if rgbStrip.STRIP_LENGHT-1-10 is not i: + rgbStrip.WS2812b(rgbStrip.STRIP_LENGHT-i-1, rgbStrip.red[rgbStrip.STRIP_LENGHT-i-2], rgbStrip.green[rgbStrip.STRIP_LENGHT-i-2], rgbStrip.blue[rgbStrip.STRIP_LENGHT-i-2]) + else: + rgbStrip.WS2812b(rgbStrip.STRIP_LENGHT-i-1, c.red, c.green, c.blue) + rgbStrip.show() + """ + rgbStrip.RGB(int(c.red),int(c.green),int(c.blue)) + # shorten the cumulative list to account for changes in dynamics + + + def pitchRainbow(self,p: float): + if p < 1: + return self.Color(0,0,0) + elif p < 255: + return self.Color(255 - p % 255,p % 255,0) + elif p < 510: + return self.Color(0,255 - p % 255,p % 255) + else: + return self.Color(p % 255,0,255 - p % 255) + + def pitchConv(self,p: float): + + r:float + g:float + b:float + + if p < 40: + return self.Color(255,0,0) + + elif p >= 40 and p <= 77 : + b = (p - 40) * (255/37.0000) + return self.Color(255,0,b) + + elif p > 77 and p <= 205: + r = 255 - ((p - 78) * 2) + return self.Color(r,0,255) + + elif p >= 206 and p <= 238: + g = (p - 206) * (255/32.0000) + return self.Color(0,g,255) + + elif p <= 239 and p <= 250: + r = (p - 239) * (255/11.0000) + return self.Color(r, 255, 255) + + elif p >= 251 and p <= 270: + return self.Color(255, 255, 255) + + elif p >= 271 and p <= 398: + rb = 255-((p-271)*2) + return self.Color(rb, 255, rb) + + elif p >= 398 and p <= 650: + return self.Color(0, 255-(p-398), (p-398)) + + else: + return self.Color(255, 0, 0) + + + class Color: + red: float + green: float + blue: float + def __init__(self,red:float,green:float,blue:float) -> None: + self.red = red + self.green = green + self.blue = blue \ No newline at end of file diff --git a/server/effects/offEffect.py b/server/Effects/OffEffect.py similarity index 82% rename from server/effects/offEffect.py rename to server/Effects/OffEffect.py index 9ea5234..63ef56c 100644 --- a/server/effects/offEffect.py +++ b/server/Effects/OffEffect.py @@ -1,9 +1,8 @@ -from rgbUtils.BaseEffect import BaseEffect -from rgbUtils.debug import debug +from Utils.BaseEffect import BaseEffect import time -class offEffect(BaseEffect): +class OffEffect(BaseEffect): name = "offEffect" desc = "LED-Band *sollte* nicht an sein" diff --git a/server/effects/onEffect.py b/server/Effects/OnEffect.py similarity index 92% rename from server/effects/onEffect.py rename to server/Effects/OnEffect.py index a2b776f..b64f879 100644 --- a/server/effects/onEffect.py +++ b/server/Effects/OnEffect.py @@ -1,9 +1,8 @@ -from rgbUtils.BaseEffect import BaseEffect -from rgbUtils.EffectParameter import slider, colorpicker -from rgbUtils.debug import debug +from Utils.BaseEffect import BaseEffect +from Utils.EffectParameter import slider, colorpicker import time -class onEffect(BaseEffect): +class OnEffect(BaseEffect): name = "onEffect" desc = "LED-Band *sollte* an sein" diff --git a/server/effects/rainbowEffect.py b/server/Effects/RainbowEffect.py similarity index 97% rename from server/effects/rainbowEffect.py rename to server/Effects/RainbowEffect.py index 188ccac..3d32149 100644 --- a/server/effects/rainbowEffect.py +++ b/server/Effects/RainbowEffect.py @@ -1,8 +1,7 @@ -from rgbUtils.BaseEffect import BaseEffect -from rgbUtils.debug import debug +from Utils.BaseEffect import BaseEffect import time -class rainbowEffect(BaseEffect): +class RainbowEffect(BaseEffect): name = "rainbowEffect" desc = "LED-Band *sollte* rainbowEffect sein" @@ -13,7 +12,6 @@ class rainbowEffect(BaseEffect): #loop effect as long as not stopped def effect(self): - debug(self) """Draw rainbow that fades across all pixels at once.""" for j in range(256*75): for rgbStrip in self.effectRGBStrips(): diff --git a/server/effects/strobeEffect.py b/server/Effects/StrobeEffect.py similarity index 91% rename from server/effects/strobeEffect.py rename to server/Effects/StrobeEffect.py index c399bc6..34a19d5 100644 --- a/server/effects/strobeEffect.py +++ b/server/Effects/StrobeEffect.py @@ -1,9 +1,8 @@ -from rgbUtils.BaseEffect import BaseEffect -from rgbUtils.debug import debug +from Utils.BaseEffect import BaseEffect from random import randint import time -class strobeEffect(BaseEffect): +class StrobeEffect(BaseEffect): name = "strobeEffect" desc = "*Strobe*" diff --git a/server/effects/__init__.py b/server/Effects/__init__.py similarity index 100% rename from server/effects/__init__.py rename to server/Effects/__init__.py diff --git a/server/rgbUtils/BaseEffect.py b/server/Utils/BaseEffect.py similarity index 86% rename from server/rgbUtils/BaseEffect.py rename to server/Utils/BaseEffect.py index 9a3d33a..6858432 100644 --- a/server/rgbUtils/BaseEffect.py +++ b/server/Utils/BaseEffect.py @@ -1,21 +1,26 @@ import time import threading +import typing import copy -from rgbUtils.debug import debug class BaseEffect(threading.Thread): # The Name and the Description of the Effect, # should be overwritten by the inheritancing Effect - name = "Undefined" - desc = "No Description" + name: str = "Undefined" + desc: str = "No Description" # Something that will be used to show descriptions and value options # of the parameters the effect will accept, in a way, that eg the webclient can decide, # if the parameters can be toggeled by a button/checkbox/slider/whatever - effectParameters = [] + effectParameters: list = [] - stop = False + # musiceffect things + # TODO we need a recorderController where the effect binds to and gets its values, this is shit... + hasNewFttValues = False + ftt: typing.Tuple[typing.List[float],typing.List[float]] = ([],[]) + + stop:bool = False def __init__(self): threading.Thread.__init__(self) @@ -48,7 +53,7 @@ class BaseEffect(threading.Thread): # to avoid two effects accessing the rgbStrip def effect(self): while 1: - debug("ET "+self.name+" effect() function needs to be replaced in inheritancing Effect") + print("ET "+self.name+" effect() function needs to be replaced in inheritancing Effect") # called when the effect is stopped def end(self): @@ -75,6 +80,10 @@ class BaseEffect(threading.Thread): def onEffectParameterValuesUpdated(self): return + def updateFttValues(self,ftt: typing.Tuple[typing.List[float],typing.List[float]]): + self.hasNewFttValues = True + self.ftt = ftt + # returns a list of the RGBStrips used by this effect def effectRGBStrips(self): return self.effectRGBStripList diff --git a/server/Utils/EffectController.py b/server/Utils/EffectController.py new file mode 100644 index 0000000..1a6da17 --- /dev/null +++ b/server/Utils/EffectController.py @@ -0,0 +1,245 @@ +# import offEffect + +from Effects.OffEffect import OffEffect +from Utils.BaseEffect import BaseEffect + +import time +import threading +import os +import sys + +#TypeCheck +from typing import Type, List +from Utils.RGBStrip import RGBStrip +from Utils.RGBStripController import RGBStripController + + +class EffectController: + ''' + the effectController handles the effects and pushes the values to the rgbStripContoller + it also calls the backendProvider's onChange function when there are changes made on 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 + ''' + + rememberRGBStripEffectThreads: dict = {} + + # list of current running effects + effectThreads: List[BaseEffect] = [] + # the always running base effect + offEffectThreadObject: BaseEffect + # maybe i will seperate this later + onControllerChangeHandler: List[Type[object]] = [] + #rgbStripController + rgbStripController: RGBStripController + + def __init__(self,rgbStripController: RGBStripController) -> None: + '''Init the EffectController with the RGBStripController''' + self.rgbStripController = rgbStripController + # load the effects + self.effectsList: List[Type[BaseEffect]] = 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() + + + def startEffect(self, effectClass: Type[BaseEffect], rgbStrips: list, params: list = []) -> BaseEffect: #TODO params as a object -> EffectParams + '''starts a effect by given class, rgbStrips and params ([[index,[param,param,param]],[index,[param,param,param]]])''' + 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 + + + def getEffects(self) -> List[Type[BaseEffect]]: + ''' + returns all effectClasses but offEffect, since offEffect will never be killed + and should not be running twice + ''' + # alle außer offEffect + return self.effectsList + + + def getEffectThreads(self) -> List[BaseEffect]: + '''returns all running effectThreads''' + return self.effectThreads + + + def getEffectRGBStrips(self, effectThreadObject: BaseEffect) -> List[RGBStrip]: + '''returns a list of the RGBStrips used by this effect''' + return effectThreadObject.effectRGBStrips() + + + def moveRGBStripToEffectThread(self, rgbStrip: RGBStrip, effectThreadObject: BaseEffect) -> None: + '''move a rgbStip to a running effectThread''' + # 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(): + self.rememberRGBStripEffectThreads[rgbStrip.STRIP_NAME] = et + 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): + # but ignore the first effectthread, since that is our offeffect that should never be killed + if len(effectThread.effectRGBStrips()) == 0 and x is not 0: + effectThread.stopEffect() + self.effectThreads.remove(effectThread) + self.noticeControllerChange() + + + def updateEffectParameters(self, effectThreadObject: BaseEffect, effectParameters: list): + '''updates parameter of a running effectThread ([[index,[param,param,param]],[index,[param,param,param]]])''' + for effectParameter in effectParameters: + effectThreadObject.updateEffectParameterValues( + effectParameter[0], effectParameter[1]) + self.noticeControllerChange() + + + def stopAll(self): + '''stops all effectThreads and set the rgbStrips to off''' + print("effectController stopAll()") + for effectThread in self.effectThreads: + print("effectController killing... "+str(effectThread)) + effectThread.stopEffect() + print("effectController killed "+str(effectThread)) + + print("effectController stopping Guardian... ") + self.effectGuardian.stop() + print("effectController stopped Guardian") + + time.sleep(0.5) + + + def noticeControllerChange(self) -> None: + '''inform the controllerChangeHandler to update the client''' + for controllerChangeHandler in self.onControllerChangeHandler: + controllerChangeHandler() + + def addOnControllerChangeHandler(self, hander: Type[object]) -> None: #TODO class instead of function for OnControllerChangeHandler? + '''add onControllerChangeHandler''' + print("addOnControllerChangeHandler", str(hander)) + self.onControllerChangeHandler.append(hander) + # send data to this client + hander() + + + def removeOnControllerChangeHandler(self, hander: Type[object]): #TODO class instead of function for OnControllerChangeHandler? + '''remove onControllerChangeHandler''' + print("removeOnControllerChangeHandler", str(hander)) + if hander in self.onControllerChangeHandler: + self.onControllerChangeHandler.remove(hander) + else: + print('\n\n -> client was never registered!') + + + def getEffectsListFromDir(self) -> List[Type[BaseEffect]]: + '''automaticly loads all modules from effects subdir and adds them to the list of effects if they have the BaseEffect as subclass''' + effectsList: List[Type[BaseEffect]] = [] + 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: RGBStrip): + ''' moves new rgbStrip to the offEffectThread and notice all Controller Clients''' + if rgbStrip.STRIP_NAME in self.rememberRGBStripEffectThreads: + restoredEffect: BaseEffect = self.rememberRGBStripEffectThreads[rgbStrip.STRIP_NAME] + if restoredEffect.isAlive(): + print("stripname in rememberRGBStripEffectThreads -> same effect was never stopped, move to") + self.moveRGBStripToEffectThread(rgbStrip,restoredEffect) + else: + print("stripname in rememberRGBStripEffectThreads -> create new effect") + + effectParamValueList: list = [] + for x in range(len(restoredEffect.effectParameterValues)): + valdict: dict = {} + for y in range(len(restoredEffect.effectParameterValues[x])): + valdict[y] = restoredEffect.effectParameterValues[x][y] + effectParamValueList.append([x,valdict]) + self.startEffect(restoredEffect.__class__,[rgbStrip],effectParamValueList) + self.rememberRGBStripEffectThreads.pop(rgbStrip.STRIP_NAME) + + + else: + print("stripname was not in rememberRGBStripEffectThreads") + self.offEffectThreadObject.addRGBStrip(rgbStrip) + self.noticeControllerChange() + + def onRGBStripUnRegistered(self,rgbStrip: RGBStrip): + ''' + cycle throught all effectThreadss and + remove Strip from effect if added + ''' + et: BaseEffect + 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): + # but ignore the first effectthread, since that is our offeffect that should never be killed + if len(effectThread.effectRGBStrips()) == 0 and x is not 0: + effectThread.stopEffect() + self.effectThreads.remove(effectThread) + self.noticeControllerChange() + + def updateFttValues(self,ftt: tuple): + ''' TODO this is a themorary funtion util a recorderController is build''' + effectThread: BaseEffect + for effectThread in self.effectThreads: + effectThread.updateFttValues(ftt) + + class EffectGuardian(threading.Thread): + ''' + a bit of failover handling, remove dead threads from effectThread array + move strips without an effect to the offEffect + + This Guardian detects Effect Threads that are killed in some unnormal way + This is not the procedere how effects are stoped + Something went wrong when the guarian is removing dead threads! + ''' + def __init__(self, effectController): + threading.Thread.__init__(self, name='EffectGuardian') + + 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 diff --git a/server/rgbUtils/EffectParameter.py b/server/Utils/EffectParameter.py similarity index 89% rename from server/rgbUtils/EffectParameter.py rename to server/Utils/EffectParameter.py index 45b117b..54e0c7c 100644 --- a/server/rgbUtils/EffectParameter.py +++ b/server/Utils/EffectParameter.py @@ -1,6 +1,6 @@ import threading -class effectParameter(object): +class EffectParameter(object): # The Name and the Description of the EffectParameter, # should be overwritten by the inheritancing EffectParameter name="Undefined" @@ -12,7 +12,7 @@ class effectParameter(object): # [min/off,max/on,current,"description"], # [min/off,max/on,current,"description"], # ] - options = [] + options: list = [] def __init__(self,name,desc,initOptions = []): self.name = name @@ -27,7 +27,7 @@ class effectParameter(object): else: return False -class colorpicker(effectParameter): +class colorpicker(EffectParameter): name="UndefinedColorpicker" desc="No Description" type="colorpicker" @@ -36,7 +36,7 @@ class colorpicker(effectParameter): def testValue(self,index,value): return True -class slider(effectParameter): +class slider(EffectParameter): name="UndefinedSlider" desc="No Description" type="slider" \ No newline at end of file diff --git a/server/rgbUtils/pyAudioRecorder.py b/server/Utils/PyAudioRecorder.py similarity index 99% rename from server/rgbUtils/pyAudioRecorder.py rename to server/Utils/PyAudioRecorder.py index dfe7e43..dd48fb8 100644 --- a/server/rgbUtils/pyAudioRecorder.py +++ b/server/Utils/PyAudioRecorder.py @@ -11,14 +11,13 @@ import numpy -import scipy import pyaudio import threading import time -class pyAudioRecorder: +class PyAudioRecorder: """Simple, cross-platform class to record from the default input device.""" def __init__(self): diff --git a/server/rgbUtils/RGBStrip.py b/server/Utils/RGBStrip.py similarity index 90% rename from server/rgbUtils/RGBStrip.py rename to server/Utils/RGBStrip.py index 4fe7388..4a2ae4d 100644 --- a/server/rgbUtils/RGBStrip.py +++ b/server/Utils/RGBStrip.py @@ -1,16 +1,19 @@ -from rgbUtils.debug import debug import uuid +import typing class RGBStrip: # name = the name off the the strip, defined by the client connecting to the server # uid = unique id, if the strip sends one, use this (later maybe, or never, whatever) # lenght = the lenght off the strip, for future use of eg WS2812b strips, will be 1 by default - def __init__(self,name,onValuesUpdateHandler,lenght=1): + STRIP_NAME: str + STRIP_LENGHT: int + def __init__(self,name: str,onValuesUpdateHandler: object,lenght: int=1,address: typing.Tuple[str,int] = ("0.0.0.0",0000)): # UID should be updateable later, or not? # when updating, be sure it does not exist self.STRIP_UID = str(uuid.uuid4()) self.STRIP_NAME = name self.STRIP_LENGHT = lenght + self.address = address self.onValuesUpdateHandler = onValuesUpdateHandler @@ -18,7 +21,7 @@ class RGBStrip: self.green = [0]*self.STRIP_LENGHT self.blue = [0]*self.STRIP_LENGHT - def RGB(self,red,green,blue,brightness = 100): + def RGB(self,red: int,green: int,blue: int,brightness: int = 100): if(red < 0): red = 0 @@ -89,7 +92,6 @@ class RGBStrip: self.onValuesUpdateHandler(self) def getData(self): - self.hasNewData = False return [self.red,self.green,self.blue] diff --git a/server/rgbUtils/rgbStripController.py b/server/Utils/RGBStripController.py similarity index 85% rename from server/rgbUtils/rgbStripController.py rename to server/Utils/RGBStripController.py index 37a6188..100fb89 100644 --- a/server/rgbUtils/rgbStripController.py +++ b/server/Utils/RGBStripController.py @@ -1,15 +1,20 @@ -from rgbUtils.RGBStrip import RGBStrip +from Utils.RGBStrip import RGBStrip import time import threading import json -class rgbStripController(): +class RGBStripController: + ''' + rgbStrips register themselves at the rgbStripContoller + the rgbStripController calls the backend Provider's onChange function + when there are new values for the strip + ''' def __init__(self): #threading.Thread.__init__(self) self.rgbStrips = [] self.onRGBStripRegisteredHandler = [] self.onRGBStripUnRegisteredHandler = [] - def registerRGBStrip(self,rgbStripName,onValuesUpdateHandler,ledcount=1): + def registerRGBStrip(self,rgbStripName: str,onValuesUpdateHandler: object,ledcount=1): # maybe we can use an unique id if the strip reconnects later, eg push the uid # to the client on first connect and if he reconnects he sould send it back again. # the wmos could use the mac adress, if there is a python script it can save the uid diff --git a/server/rgbUtils/__init__.py b/server/Utils/__init__.py similarity index 100% rename from server/rgbUtils/__init__.py rename to server/Utils/__init__.py diff --git a/server/rgbUtils/effectController.py b/server/rgbUtils/effectController.py deleted file mode 100644 index 00a268e..0000000 --- a/server/rgbUtils/effectController.py +++ /dev/null @@ -1,183 +0,0 @@ -# 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() - - # - a bit of failover handling, remove dead threads from effectThread array - # - move strips without an effect to the offEffect - class effectGuardian(threading.Thread): - def __init__(self, effectController): - threading.Thread.__init__(self, name='effectGuardian') - - 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