import threading import socketserver import socket import traceback from time import sleep, time import json import struct class ThreadedUDPServer(threading.Thread): def __init__(self, effectController, rgbStripController): threading.Thread.__init__(self, name="ThreadedUDPServer") self.effectController = effectController self.rgbStripController = rgbStripController self.daemon = True self.stopped = False self.start() self.udpClientGuardian = self.UDPClientGuardian() self.udpClientGuardian.start() UDPClients def run(self): self.server = socketserver.UDPServer(('', 8002), UDPStripHandler) self.server.effectController = self.effectController self.server.rgbStripController = self.rgbStripController self.server.serve_forever() print("ThreadedUDPServer stopped") def stop(self): self.udpClientGuardian.stop() self.server.shutdown() # check last pings from clients, responds with pong and remove clients # when there is no answer after 2 seconds class UDPClientGuardian(threading.Thread): def __init__(self): threading.Thread.__init__(self,name="UDPClientGuardian") self.stopped = False def run(self): while not self.stopped: for key in list(UDPClients.keys()): if UDPClients[key].lastping + 2 < time(): print("ping missing, last ping: ",UDPClients[key].lastping," now is: ",time()) UDPClients[key].handleClose() del UDPClients[key] sleep(0.5) def stop(self): self.stopped = True CLIENT_TYPE_CONTROLLER = 0 CLIENT_TYPE_STRIPE = 1 CLIENT_TYPE_RECORDER = 2 UDPClients: dict = {} class UDPStripHandler(socketserver.BaseRequestHandler): def handle(self): # print(self.client_address) if self.client_address not in UDPClients: UDPClients[self.client_address] = UDPClient( self.client_address, self.server.effectController, self.server.rgbStripController) UDPClients[self.client_address].handle(self.request) class UDPClient(): def __init__(self, client_address, effectController, rgbStripController): self.client_type = None self.rgbStrip = None self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.client_address = client_address self.effectController = effectController self.rgbStripController = rgbStripController self.sendToClientLock = False self.registered = False #Recorder related self.xs: typing.List[float] = [] self.ys: typing.List[float] = [] 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 # CLIENT_TYPE_RECORDER = 2 # UDP Registrierung Stripe: (nosend definiert, dass der stripe sich seine daten selbst anfordert) # r:1:NUM_LEDS[:nosend] # UDP Stripe sendet ping, woran festgemacht wird, ob er nocht lebt # s:ping # UDP try: data = clientdata.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: self.registered = True self.client_type = CLIENT_TYPE_STRIPE # registers the strip with websocket object and name. the onRGBStripValueUpdate(rgbStrip) is called by # by the rgbStrip when an effectThread updates it # the self.rgbStrip variable is used to unregister the strip only ledcount = 1 if data[3] != None: ledcount = int(data[3]) self.rgbStrip = self.rgbStripController.registerRGBStrip( data[2], self.onRGBStripValueUpdate, ledcount) if data[0] == "r" and int(data[1]) == CLIENT_TYPE_RECORDER and data[2] != None and self.registered is False: self.registered = True self.client_type = CLIENT_TYPE_RECORDER print("RECORDER CONNECTED") ''' TODO a recorderController to register the Controller self.rgbStrip = self.rgbStripController.registerRGBStrip( data[2], self.onRGBStripValueUpdate, ledcount)''' # s:ping if data[0] == "s" and data[1] == "ping": # if we got a ping and the client has no client type defined, send status unregistered, so the client knows that he has to register if self.client_type is None and self.socket is not None: self.sendToClient('sr') self.lastping = time() if self.client_type == CLIENT_TYPE_RECORDER: if data[0] == "dlen": #print(data) self.xs = [0]*int(data[1]) self.ys = [0]*int(data[2]) if data[0] == "d" and data[1] == "xs" and self.xs.__len__ != 0: for item in data[2:]: index,val = item.split("|") self.xs[int(index)] = float(val) if data[0] == "d" and data[1] == "ys" and self.ys.__len__ != 0: for item in data[2:]: index,val = item.split("|") self.ys[int(index)] = float(val) if data[0] == "d" and data[1] == "c": #print([self.xs,self.ys]) self.effectController.updateFttValues((self.xs,self.ys)) except Exception as e: print(e, traceback.format_exc()) # unregister the onChangeHandler # for now this function is not called when a client times out, # so they don't get unregistered. i mean there is no function that # is called when a client times out. def handleClose(self): if self.client_type is CLIENT_TYPE_STRIPE: self.rgbStripController.unregisterRGBStrip(self.rgbStrip) print(self.client_address, 'closed') # when a rgbStrip value is changed, send not json data but a formated string to client # d:[id off the LED, always 0 on RGB strips]:[red value 0-255]:[green value 0-255]:[blue value 0-255] def onRGBStripValueUpdate(self, rgbStrip): respdata = "d" tmplen = 0 for i in range(rgbStrip.STRIP_LENGHT): if tmplen is 49: self.sendToClient(respdata) respdata = "d" tmplen = 0 respdata = respdata + "{0:03}".format(i) + "{0:03}".format(rgbStrip.red[i]) + "{0:03}".format(rgbStrip.green[i]) + "{0:03}".format(rgbStrip.blue[i]) tmplen = tmplen+1 self.sendToClient(respdata) self.sendToClient('su') def sendToClient(self, message): #print("SendToClient:",self.client_address, message) self.socket.sendto( message.encode(), self.client_address )