import threading import socketserver import socket import traceback from time import sleep, time import json import struct from rgbUtils import effectControllerJsonHandler from rgbUtils import rgbStripControllerJsonHandler class ThreadedUDPServer(threading.Thread): def __init__(self,effectController,rgbStripController): threading.Thread.__init__(self) 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) self.stopped = False def run(self): while not self.stopped: for key in list(UDPClients.keys()): if UDPClients[key].lastping + 2 < 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 = {} 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.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) 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: 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 self.rgbStrip = self.rgbStripController.registerRGBStrip(data[2],self.onRGBStripValueUpdate) #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('s:unregistered') self.lastping = time() 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 json data to client def onRGBStripValueUpdate(self,rgbStrip,led = 0): self.sendToClient('d:'+str(led)+':'+str(rgbStrip.red[led])+':'+str(rgbStrip.green[led])+':'+str(rgbStrip.blue[led])+'') def sendToClient(self,message): while self.sendToClientLock is True: sleep(1) self.sendToClientLock = True if self.socket is not None: self.socket.sendto( message.encode(), self.client_address ) self.sendToClientLock = False # class UDPStripHandler: # def __init__(self): # #the rgbStrip object returned by the # self.rgbStrip = None # # last ping command from the client # self.lastPing = 0 # def handleMessage(self): # try: # print(self.address, self.data) # data = json.loads(self.data) # # Client Registration on the Websocket Server # # maybe it would be better to use a websocket server thread for each client type, # # can be done in future if there is too much latency # if "register_client_type" in data: # # the controler type, add handler on RGBStripContoller and send the current state of the controller # if int(data['register_client_type']) is CLIENT_TYPE_CONTROLLER: # self.client_type = CLIENT_TYPE_CONTROLLER # # add effectController onControllerChangeHandler to get changes in the effectController eg start/stop effects, parameter updates, moved strips # # register rgbStripController onRGBStripRegistered/UnRegistered handler to get noticed about new rgbStrips is not necessary # # since we will get noticed from the effectController when it added the rgbStrip to the offEffect # self.effectController.addOnControllerChangeHandler(self.onChange) # # register new Stripes # elif int(data['register_client_type']) is CLIENT_TYPE_STRIPE and "client_name" in data: # 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 # self.rgbStrip = self.rgbStripController.registerRGBStrip(data["client_name"],self.onRGBStripValueUpdate) # # register new Audio Recorders # elif int(data['register_client_type']) is CLIENT_TYPE_RECORDER: # self.client_type = CLIENT_TYPE_RECORDER # # controller responses are handled by the effectControllerJsonHandler # if self.client_type is CLIENT_TYPE_CONTROLLER: # response = effectControllerJsonHandler.responseHandler(self.effectController, self.rgbStripController, data) # self.sendMessage( # json.dumps({ # 'response': response # }) # ) # return # # the stripe should usualy not send any data, i do not know why it should... # elif self.client_type is CLIENT_TYPE_STRIPE: # return # # audio recorder responses are handled by the effectControllerJsonHandler # elif self.client_type is CLIENT_TYPE_RECORDER: # return # except Exception as e: # print(e, traceback.format_exc())