class EvalClient(): POSITIONS = ['1 2 3', '3 2 1', '2 3 1', '3 1 2', '1 3 2', '2 1 3'] ACTIONS = ['gun', 'sidepump', 'hair'] def __init__(self, host: str, port: int, controlMain): self.controlMain = controlMain self.server = (host, port) self.encryptionHandler = EncryptionHandler(b'Sixteen byte key') pass def sendToEval(self, positions=None, action=None, sync_delay=None, quit=False): if quit: message = '# |logout| ' else: message = '#' + self.POSITIONS[positions] + '|' + self.ACTIONS[ action] + '|' + '0.005' message = self.encryptionHandler.encrypt_msg(message) self.evalSocket.send(message) if quit: return data = self.evalSocket.recv(1024).decode() print('Received from server: ' + data) def connectToEval(self): self.evalSocket = socket.socket() print(self.server) self.evalSocket.connect(self.server)
from Util.encryption import EncryptionHandler h = EncryptionHandler(b'Sixteen Byte Key') message = "caleb" em = h.encrypt_msg(message) print(h.decrypt_message(em)) print(b"R\x9e\xc6!\x01\x05\xcf\xa6&\xac\x9dQ\xc8\x92\xe5\xb4".decode('utf8'))
class Ultra96Server: # Tuple containing "host" and "port" values for ultra96 server connection = () # Holds socket address and port for each dancer, tied to dancer id as key clients = {} # Class for handling AES encryption encryptionhandler = None # Holds last timestamps from the 3 dancers/laptops currTimeStamps = {} # Holds the last 10 recorded offsets from the 3 dancers # 2d array containing 10 lists of 3 offsets from each dancer last10Offsets = {} # Used to iterate offset list from the back in order to update offsets currIndexClockOffset = {} # Holds average offsets for 3 dancers, calculated from last10Offsets currAvgOffsets = {} # Booleans to check if current moves have been received for each dancer currentMoveReceived = {} # Count to keep track of number of clock sync updates sent from each client # in current rotation (1-10) clocksyncCount = {} offsetLock = threading.Lock() timestampLock = threading.Lock() moveRcvLock = threading.Lock() # Initialize encryption handler and socket data + misc setup def __init__(self, host:str, port:int, key:bytes): self.connection = (host,port) self.encryptionhandler = EncryptionHandler(key.encode()) # for _ in range(10): # self.last10Offsets.append([None,None,None]) return def updateTimeStamp(self, message : str, dancerID): print("Evaluating move...") print(f"time recorded by bluno:", {message}) #calculate relative time using offset timestamp = float(message) relativeTS = timestamp - self.currAvgOffsets[dancerID] self.timestampLock.acquire() self.currTimeStamps[dancerID] = relativeTS self.timestampLock.release() return def broadcastMessage(self, message): message = self.encryptionhandler.encrypt_msg(message) for conn, addr in self.clients.values(): print("BROADCAST: ",conn) conn.send(message) def respondClockSync(self, message : str, dancerID, timerecv): print(f"Received clock sync request from dancer, {dancerID}") timestamp = message print(f"t1 =",{timestamp}) conn, addr = self.clients[dancerID] # response = str(timerecv) + "|" + str(time.time()) response = json.dumps({'command' : 'CS', 'message': str(timerecv) + '|' + str(time.time())}) conn.send(response.encode()) # Must be called after acquiring offsetlock def updateAvgOffset(self): for dancerID, offsetList in self.last10Offsets.items(): currSum = 0 numOffsets = 10 for offset in offsetList: if offset is None: numOffsets -= 1 continue currSum += offset if numOffsets == 0: continue self.currAvgOffsets[dancerID] = currSum/numOffsets # Check if variance between 10 offsets in dancerID is too high. # If so, force another 10 updates with the specific dancerID def checkOffsetVar(self, dancerID): conn,addr = self.clients[dancerID] prevOffset = self.currAvgOffsets[dancerID] self.updateAvgOffset() clockDriftDetected = False offsetDiff = 0 if not prevOffset is None: offsetDiff = abs(self.currAvgOffsets[dancerID] - prevOffset) clockDriftDetected = (offsetDiff > 5e-05) varLast10 = variance(self.last10Offsets[dancerID]) print("VARIANCE FOR DANCER: ", dancerID, varLast10) if varLast10 > 1e-05 or clockDriftDetected: print("Offset variance too high: ",varLast10," or clock drift too high:", offsetDiff, "Resyncing for Dancer: ", dancerID) conn.send(self.encryptionhandler.encrypt_msg("sync")) return def updateOffset(self, message: str, dancerID): self.offsetLock.acquire() print(f"{dancerID} has received offsetlock") self.last10Offsets[dancerID][self.currIndexClockOffset[dancerID]] = float(message) self.currIndexClockOffset[dancerID] = (self.currIndexClockOffset[dancerID] - 1) % 10 print(f"{dancerID} is releasing offsetlock") self.offsetLock.release() if self.clocksyncCount[dancerID] != 10: self.clocksyncCount[dancerID] += 1 if self.clocksyncCount[dancerID] == 10: self.checkOffsetVar(dancerID) self.clocksyncCount[dancerID] = 0 print("Updating dancer " + str(dancerID) + " offset to: " + message + "\n") return def calculateSyncDelay(self): sortedTimestamps = sorted(self.currTimeStamps.values()) return (sortedTimestamps[2] - sortedTimestamps[0]) def handleServerInput(self): command = input("The rest of this test script will be controlled via " + "server side input. Type 'sync' to perform clock synchronization " + "protocol and 'start' to broadcast start signal to all laptops/dancers\n") while command != "quit": if command == "sync": self.broadcastMessage("sync") if command == "start": self.broadcastMessage("start") command = input("Enter 'sync' to start clock sync and 'start' "+ "to send start signal for move eval") self.broadcastMessage("quit") def handleClient(self, dancerID : str): conn,addr = self.clients[dancerID] try: while True: data = conn.recv(1024) timerecv = time.time() data = self.encryptionhandler.decrypt_message(data) data = json.loads(data) print("Received data:" + json.dumps(data) + "\n") # print(data.decode("utf8")) if data['command'] == "shutdown": print(dancerID, ' Received shutdown signal\n') break elif data['command'] == "clocksync": print("cLoCkSyNc") self.respondClockSync(data['message'], dancerID, timerecv) elif data['command'] == "offset": self.updateOffset(data['message'], dancerID) elif data['command'] == "evaluateMove": self.updateTimeStamp(data['message'], dancerID) self.moveRcvLock.acquire() self.currentMoveReceived[dancerID] = True if all(value == True for value in self.currentMoveReceived.values()): print(f"Sync delay calculated:", {self.calculateSyncDelay()}) self.currentMoveReceived = {key: False for key in self.currentMoveReceived.keys()} self.moveRcvLock.release() # decrypted_msg = encryptionHandler.decrypt_message(data) print(dancerID, " RETURNING\n") except: print("[ERROR][", dancerID, "] -> ", sys.exc_info()) # Initialize connections with 3 dancers prior to start of evaluation def initializeConnections(self, numDancers = 3): mySocket = socket.socket() # host,port = self.connection mySocket.bind((self.connection)) mySocket.listen(5) try: for _ in range(numDancers): conn,addr = mySocket.accept() data = self.encryptionhandler.decrypt_message(conn.recv(4096)) print("Dancer ID: ", data) self.clients[data] = (conn,addr) print(addr, '\n') self.currIndexClockOffset[data] = 9 # initialize index counter to 9 for each dancer self.last10Offsets[data] = [None for _ in range(10)] # initialize last 10 offsets for dancer id to None self.currAvgOffsets[data] = None self.currentMoveReceived[data] = False self.clocksyncCount[data] = 0 return except: print(sys.exc_info()[0], "\n") return
class Dancer(): def __init__(self, dancerID: int, conn: socket.socket): self.dancerID = dancerID self.conn = conn self.clockSyncResponseLock = threading.Event( ) # To handle client-server synchronization for clock sync protocol self.encryptionHandler = EncryptionHandler(b'Sixteen byte key') self.currIndexClockOffset = 9 self.last10Offsets = [None for _ in range(10)] self.currAvgOffset = None self.clockSyncCount = 0 self.dataQueue = Queue() def handleClient(self, dataQueue, dataQueueLock: threading.Event, positionChange: list): self.dataQueue = dataQueue self.dataQueueLock = dataQueueLock conn = self.conn while True: try: receivedFromBuffer = self.recvall(conn) timerecv = time.time() packets = receivedFromBuffer.decode('utf8').split( ",") # Split into b64encoded packets by ',' delimiter packets.pop(-1) # Pop last empty "packet" for packet in packets: data = self.encryptionHandler.decrypt_message(packet) if not data: continue data = json.loads(data) # print("Received data:" + json.dumps(data) + "\n") # print(data.decode("utf8")) if data['command'] == "shutdown": print(self.dancerID, ' Received shutdown signal\n') return elif data['command'] == "clocksync": self.respondClockSync(data['message'], conn, timerecv, self.dancerID) elif data['command'] == "offset": self.updateOffset(data['message']) self.clockSyncResponseLock.set() elif data['command'] == "timestamp": # unlock data queue to store data self.dataQueueLock.clear() self.updateTimeStamp(data['message']) elif data['command'] == "data": data.pop('command') data.pop('PosChangeFlag') self.addData(data) elif data['command'] == "poschange": self.updatePosition(data['message'], positionChange) except Exception as e: print("[ERROR][HANDLECLIENT]:", self.dancerID, e) return def updatePosition(self, data, positionChange: list): change = int(data) positionChange[self.dancerID] += change print("Dancer", self.dancerID, "Received position change data", data, "\nNewData: ", positionChange) return def updateTimeStamp(self, data): timestamp = float(data) relativeTimeStamp = timestamp - self.currAvgOffset self.currTimeStamp = relativeTimeStamp print("Dancer: ", self.dancerID, "bluno recorded TS: ", timestamp, "adjusted TS: ", self.currTimeStamp) def addData(self, data): if not self.dataQueueLock.is_set(): self.dataQueue.put(data) else: while not self.dataQueue.empty(): self.dataQueue.get() def recvall(self, conn: socket.socket): fullMessageReceived = False data = b'' while not fullMessageReceived: data += conn.recv(1024) if data[-1] == 44: # 44 corresponds to ',' which is delimiter for end of b64 encoded msg fullMessageReceived = True return data def respondClockSync(self, message: str, conn: socket.socket, timerecv: float, dancerID): # print(f"Received clock sync request from dancer, {dancerID}") timestamp = message # print(f"t1 =",{timestamp}) response = json.dumps({ 'command': 'clocksync', 'message': str(timerecv) + '|' + str(time.time()) }) conn.send(self.encryptionHandler.encrypt_msg(response)) def sendMessage(self, message: str): message = self.encryptionHandler.encrypt_msg(message) self.conn.send(message) def handleClockSync(self, doClockSync: threading.Event, clockSyncHandlerCount, clockSyncCountLock): while True: try: doClockSync.wait() for _ in range(10): self.sendMessage('sync') self.clockSyncResponseLock.clear() time.sleep(0.05) self.clockSyncResponseLock.wait() clockSyncCountLock.acquire() if clockSyncHandlerCount[0] > 1: # clockSyncCountLock.acquire() clockSyncHandlerCount[0] -= 1 # print(self.dancerID, "done", clockSyncHandlerCount) clockSyncCountLock.release() while clockSyncHandlerCount[0] != 3: pass # print(self.dancerID, "PASS", clockSyncHandlerCount) elif clockSyncHandlerCount[0] == 1: doClockSync.clear() clockSyncHandlerCount[0] = 3 clockSyncCountLock.release() # doClockSync.clear() except Exception as e: print(e) return def updateOffset(self, message: str): self.last10Offsets[self.currIndexClockOffset] = float(message) self.currIndexClockOffset = (self.currIndexClockOffset - 1) % 10 if self.clockSyncCount != 10: self.clockSyncCount += 1 if self.clockSyncCount == 10: self.updateAvgOffset() self.clockSyncCount = 0 # print("Updating dancer " + str(self.dancerID) + " offset to: " + message + "\n") return def updateAvgOffset(self): offsetSum = 0 numOffsets = 10 for offset in self.last10Offsets: offsetSum += offset self.currAvgOffset = offsetSum / numOffsets print("Dancer: ", str(self.dancerID), "average offset: ", self.currAvgOffset)
class Ultra96Server(): def __init__(self, host: str, port: int, key: str): # each value in list holds dancer class self.addr = (host, port) self.dancers = [Dancer(0, None), Dancer(0, None), Dancer(0, None)] self.encryptionHandler = EncryptionHandler(key.encode('utf8')) self.globalShutDown = threading.Event() def initializeConnections(self, numDancers=NUM_DANCERS): socket96 = socket.socket() # host,port = self.connection socket96.bind((self.addr)) socket96.listen(3) try: for _ in range(numDancers): conn, addr = socket96.accept() messageRecv = self.recvall(conn) dancerID = self.encryptionHandler.decrypt_message(messageRecv) print("Accepted connection from:", addr, "Dancer ID: ", dancerID) dancerID = int(dancerID) if dancerID not in [0, 1, 2]: raise Exception( "Dancer ID invalid, must be integer value 0,1 or 2") self.dancers[dancerID] = Dancer(dancerID, conn) except Exception as e: print("[ERROR][initializeConnections]", e) def executeClientHandlers(self, executor, dataQueues, dataQueueLock, positionChange): dancerID = 0 for dancer in self.dancers: executor.submit(dancer.handleClient, dataQueues[dancerID], dataQueueLock, positionChange) dancerID += 1 def executeClockSyncHandlers(self, executor, doClockSync: threading.Event): self.clockSyncHandlerCount = [3] self.clockSyncCountLock = threading.Lock() for dancer in self.dancers: executor.submit(dancer.handleClockSync, doClockSync, self.clockSyncHandlerCount, self.clockSyncCountLock) def recvall(self, conn: socket.socket): fullMessageReceived = False data = b'' while not fullMessageReceived: data += conn.recv(1024) if data[-1] == 44: # 44 corresponds to ',' which is delimiter for end of b64 encoded msg fullMessageReceived = True return data def broadcastMessage(self, message): print("BROADCASTING: ", message) message = self.encryptionHandler.encrypt_msg(message) for dancer in self.dancers: dancer.conn.send(message) def getSyncDelay(self) -> float: timeStampList = [] for dancer in self.dancers: timeStampList.append(dancer.currTimeStamp) sortedTimeStamps = sorted(timeStampList) return sortedTimeStamps[NUM_DANCERS - 1] - sortedTimeStamps[0]
class LaptopClient(): def __init__(self, host, port, dancerID): self.moveStarted = Event() self.evalStarted = Event() self.socketLock = Lock() self.host = host self.port = port self.encryptionHandler = EncryptionHandler(b'Sixteen byte key') self.dancerID = dancerID def sendMessage(self, message): print("SENDING: ", message) encrypted_message = self.encryptionHandler.encrypt_msg( message ) + b',' #Send b64encoded bytes with ',' delimiter as ',' is not valid b64encoded char self.mySocket.send(encrypted_message) def handleBlunoData(self, inputQueue): try: packet = None while True: packet = None packet = inputQueue.get() if packet is not None: if not self.evalStarted.is_set(): # print("Eval not yet started, ignoring data") # print(packet) continue if packet['PosChangeFlag'] != 0: self.sendMessage( json.dumps({ "command": "poschange", "message": packet['PosChangeFlag'] })) if packet['moveFlag'] == 1: if not self.moveStarted.is_set(): # if move hasn't started, set flag and send timestamp self.moveStarted.set() self.sendMessage( json.dumps({ "command": "timestamp", "message": packet['time'] })) packet['command'] = 'data' self.sendMessage(json.dumps(packet)) # print(packet) else: print("No packet found...") time.sleep(0.04) except Exception as e: print("HANDLEBLUNO:", e) sys.exit() def startClockSync(self): self.timeSend = time.time() messagedict = {"command": "clocksync", "message": str(self.timeSend)} print("SENDING", messagedict) self.sendMessage(json.dumps(messagedict)) def respondClockSync(self, timestamps, timeRecv): timestamps = json.loads(timestamps)['message'].split('|') print(f"t1:", {self.timeSend}, "t2:", \ {timestamps[0]}, "t3:", {timestamps[1]}, "t4:", {timeRecv}) t = [ self.timeSend, float(timestamps[0]), float(timestamps[1]), timeRecv ] roundTripTime = (t[3] - t[0]) - (t[2] - t[1]) print("RTT:", {roundTripTime}) clockOffset = ((t[2] - t[3] - roundTripTime / 2) + (t[1] - t[0] - roundTripTime / 2)) / 2 messagedict = {"command": "offset", "message": str(clockOffset)} self.sendMessage(json.dumps(messagedict)) print("Clock offset:", {clockOffset}) print("\n") def handleServerCommands(self): command = self.mySocket.recv(4096) timeRecv = time.time() command = self.encryptionHandler.decrypt_message(command) print("COMMAND RECEIVED:", command) while command != "quit": if command == "sync": self.startClockSync() elif command == "start": self.evalStarted.set() elif command == 'moveComplete': self.moveStarted.clear() elif "clocksync" in command: self.respondClockSync(command, timeRecv) command = self.mySocket.recv(4096) timeRecv = time.time() command = self.encryptionHandler.decrypt_message(command) print("COMMAND RECEIVED:", command) print("Shutting down dancer number " + self.dancerID) self.sendMessage(json.dumps(SHUTDOWNCOMMAND)) self.mySocket.close() print("Quitting now") sys.exit() def connectAndIdentify(self, host, port, dancerID): self.mySocket = socket.socket() self.mySocket.connect((host, port)) print(dancerID, ": Connection established with ", (host, port)) self.sendMessage(dancerID) def start(self, remote=False): if remote: REMOTE_SERVER_IP = 'sunfire.comp.nus.edu.sg' PRIVATE_SERVER_IP = '137.132.86.228' username = input("Enter ssh username: "******"Enter ssh password: ") key = 'Sixteen byte key' #remember to hide tunnel1 = sshtunnel.open_tunnel( (REMOTE_SERVER_IP, 22), remote_bind_address=(PRIVATE_SERVER_IP, 22), ssh_username=username, ssh_password=password, # local_bind_address=('127.0.0.1', 8081), block_on_close=False) tunnel1.start() print('[Tunnel Opened] Tunnel into Sunfire opened ' + str(tunnel1.local_bind_port)) tunnel2 = sshtunnel.open_tunnel( ssh_address_or_host=( 'localhost', tunnel1.local_bind_port), # ssh into xilinx remote_bind_address=('127.0.0.1', 10022), # binds xilinx host ssh_username='******', ssh_password='******', local_bind_address=('127.0.0.1', 10022), # localhost to bind it to block_on_close=False) tunnel2.start() self.connectAndIdentify('127.0.0.1', 10022, self.dancerID) else: self.connectAndIdentify('127.0.0.1', 10022, self.dancerID)
print('Address: ' + str(tunnel1.local_bind_address)) print('Port no: ' + str(tunnel1.local_bind_port)) with SSHTunnelForwarder(('localhost', 10022), ssh_username='******', ssh_password='******', remote_bind_address=('localhost', 10022), local_bind_address=('localhost', 8080)) as tunnel2: print('Tunnel opened to ultra96 board with...') print('Address: ' + str(tunnel2.local_bind_address)) print('Port no: ' + str(tunnel2.local_bind_port)) host = '127.0.0.1' port = 8080 mySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) mySocket.connect((host, port)) message = input(" -> ") while message != 'q': encryptionHandler = EncryptionHandler(key.encode()) encrypted_msg = encryptionHandler.encrypt_msg(message) mySocket.send(encrypted_msg) data = mySocket.recv(1024).decode() print('Received from server: ' + data) message = input(" -> ") mySocket.close() print('Closing connection')
class EvalClient(): def __init__(self, host: str, port: int, controlMain): self.moveCounter = 0 self.controlMain = controlMain self.server = (host, port) self.encryptionHandler = EncryptionHandler(b'Sixteen byte key') pass def sendToEval(self, positions=randint(0, 5), action=None, sync_delay=0, quit=False): if self.moveCounter == 24: quit = True if quit: message = '# |logout| ' else: if positions == None: positions = randint(0, 5) message = '#' + POSITIONS[positions] + '|' + ACTIONS[ action] + '|' + str(sync_delay) message = self.encryptionHandler.encrypt_msg(message) self.evalSocket.send(message) if quit: return data = self.evalSocket.recv(1024).decode() print('Received from server: ' + data) self.moveCounter += 1 return data def updateDancerPositions(self, dancerPositions, positionChange): middleDancerIndex = int(dancerPositions[1]) - 1 leftDancerIndex = int(dancerPositions[0]) - 1 rightDancerIndex = int(dancerPositions[2]) - 1 # middle dancer doesn't move if positionChange[middleDancerIndex] == 0: # all dancers don't move if positionChange[rightDancerIndex] == 0 and positionChange[ leftDancerIndex] == 0: print("[UPDATE DANCER POSITIONS][NO CHANGE]", dancerPositions) positionChange = [0, 0, 0] return elif positionChange[rightDancerIndex] < 0 and positionChange[ leftDancerIndex] > 0: temp = dancerPositions[0] dancerPositions[0] = dancerPositions[2] dancerPositions[2] = temp print( "[UPDATE DANCER POSITIONS][RIGHT AND LEFT SWAP POSITIONS]", dancerPositions) else: print("[UPDATE DANCER POSITIONS][INVALID CHANGE]", dancerPositions) # middle dancer moves right elif positionChange[middleDancerIndex] > 0: if positionChange[leftDancerIndex] > 0 and positionChange[ rightDancerIndex] < 0: dancerPositions[2] = middleDancerIndex + 1 dancerPositions[0] = rightDancerIndex + 1 dancerPositions[1] = leftDancerIndex + 1 print( "[UPDATE DANCER POSITIONS][MIDDLE & LEFT DANCERS MOVE RIGHT, RIGHT DANCER MOVES LEFT", dancerPositions) elif positionChange[leftDancerIndex] == 0 and positionChange[ rightDancerIndex] < 0: dancerPositions[2] = middleDancerIndex + 1 dancerPositions[1] = rightDancerIndex + 1 print( "[UPDATE DANCER POSITIONS][LEFT DANCER STAYS, MIDDLE DANCER MOVES RIGHT, RIGHT DANCER MOVES LEFT", dancerPositions) else: print("[UPDATE DANCER POSITIONS][INVALID CHANGE]", dancerPositions) #middle dancer moves left else: if positionChange[leftDancerIndex] > 0 and positionChange[ rightDancerIndex] < 0: dancerPositions[0] = middleDancerIndex + 1 dancerPositions[1] = rightDancerIndex + 1 dancerPositions[2] = leftDancerIndex + 1 print( "[UPDATE DANCER POSITIONS][MIDDLE & RIGHT DANCERS MOVE LEFT, LEFT DANCER MOVES RIGHT", dancerPositions) elif positionChange[rightDancerIndex] == 0 and positionChange[ leftDancerIndex] > 0: dancerPositions[0] = middleDancerIndex + 1 dancerPositions[1] = leftDancerIndex + 1 print( "[UPDATE DANCER POSITIONS][RIGHT DANCER STAYS, MIDDLE DANCER MOVES LEFT, LEFT DANCER MOVES RIGHT", dancerPositions) else: print("[UPDATE DANCER POSITIONS][INVALID CHANGE]", dancerPositions) print("Positions updated, resetting position change") for x in range(len(positionChange)): positionChange[x] = 0 def handleEval(self, outputForEval, rdyForEval: threading.Event, server: Ultra96Server, globalShutDown: threading.Event, dancerPositions: list, positionChange: list, doClockSync: threading.Event): while True: if globalShutDown.is_set(): return try: rdyForEval.wait() self.updateDancerPositions(dancerPositions, positionChange) outputForEval['delay'] = server.getSyncDelay() positionsStr = '' for dancerPosition in dancerPositions: positionsStr += str(dancerPosition) + ' ' positionsStr = positionsStr.strip() outputForEval['positions'] = POSITIONS_DICT[positionsStr] print("SENDING TO EVAL SERVER: ", outputForEval) servResponse = self.sendToEval(outputForEval['positions'], outputForEval['action'], outputForEval['delay']) servResponse = servResponse.replace(',', '') servResponse = servResponse.replace('[', '') servResponse = servResponse.replace(']', '') servResponse = servResponse.replace('\'', '') servResponse = servResponse.replace(' ', '') dancerPositions[0] = int(servResponse[0]) dancerPositions[1] = int(servResponse[1]) dancerPositions[2] = int(servResponse[2]) print("Server updated values:", dancerPositions) rdyForEval.clear() # doClockSync.set() server.broadcastMessage("moveComplete") except Exception as e: print("[ERROR][EVALCLIENT]", e) return def connectToEval(self): self.evalSocket = socket.socket() print(self.server) self.evalSocket.connect(self.server)
class Ultra96Server(): # Tuple containing "host" and "port" values for ultra96 server connection = () # Holds socket address and port for each dancer, tied to dancer id as key clients = {} # Class for handling AES encryption encryptionHandler = None # Holds last timestamps from the 3 dancers/laptops currTimeStamps = {} # Holds the last 10 recorded offsets from the 3 dancers # 2d array containing 10 lists of 3 offsets from each dancer last10Offsets = {} # Used to iterate offset list from the back in order to update offsets currIndexClockOffset = {} # Holds average offsets for 3 dancers, calculated from last10Offsets currAvgOffsets = {} # Booleans to check if current moves have been received for each dancer currentMoveReceived = {} # Count to keep track of number of clock sync updates sent from each client # in current rotation (1-10) clocksyncCount = {} # To synchronize clock sync broadcasts and offset receiving clockSyncResponseLock = {} def __init__(self, host: str, port: int, key: str, controlMain): self.controlMain = controlMain self.connection = (host, port) self.encryptionHandler = EncryptionHandler(key.encode()) self.lockDataQueue = controlMain.lockDataQueue self.doClockSync = controlMain.doClockSync self.dancerDataDict = controlMain.dancerDataDict self.moveCompletedFlag = controlMain.moveCompletedFlag self.globalShutDown = controlMain.globalShutDown return def recvall(self, conn: socket.socket): fullMessageReceived = False data = b'' while not fullMessageReceived: data += conn.recv(1024) if data[-1] == 44: # 44 corresponds to ',' which is delimiter for end of b64 encoded msg fullMessageReceived = True return data def initializeConnections(self, numDancers=NUM_DANCERS): mySocket = socket.socket() # host,port = self.connection mySocket.bind((self.connection)) mySocket.listen(5) try: for _ in range(numDancers): conn, addr = mySocket.accept() print(conn, addr) # data = conn.recv(4096) data = self.recvall(conn) print(data) data = self.encryptionHandler.decrypt_message(data) print("Dancer ID: ", data) self.clients[data] = (conn, addr) print(addr, '\n') self.currIndexClockOffset[ data] = 9 # initialize index counter to 9 for each dancer self.last10Offsets[data] = [ None for _ in range(10) ] # initialize last 10 offsets for dancer id to None self.currAvgOffsets[data] = None self.currentMoveReceived[data] = False self.clocksyncCount[data] = 0 self.dancerDataDict[data] = Queue() self.clockSyncResponseLock[data] = threading.Event() return except: print(sys.exc_info(), "\n") return def calculateSyncDelay(self): sortedTimestamps = sorted(self.currTimeStamps.values()) return (sortedTimestamps[-1] - sortedTimestamps[0]) def addData(self, dancerID, data): with self.lockDataQueue: if not self.moveCompletedFlag.is_set(): self.dancerDataDict[dancerID].put(data) else: while not self.dancerDataDict[dancerID].empty(): self.dancerDataDict[dancerID].get() def updateTimeStamp(self, message: str, dancerID): print("Evaluating move...") print(f"time recorded by bluno:", {message}) #calculate relative time using offset timestamp = float(message) relativeTS = timestamp - self.currAvgOffsets[dancerID] self.currTimeStamps[dancerID] = relativeTS print(dancerID, "adjusted timestamp: ", relativeTS) def handleClient(self, dancerID: str): conn, addr = self.clients[dancerID] while True: if self.globalShutDown.is_set(): return try: # receivedFromBuffer = conn.recv(4096) receivedFromBuffer = self.recvall(conn) timerecv = time.time() packets = receivedFromBuffer.decode('utf8').split( ",") #Split into b64encoded packets by ',' delimiter packets.pop(-1) # print("data received at ", timerecv, data) for packet in packets: data = self.encryptionHandler.decrypt_message(packet) if not data: continue data = json.loads(data) # print("Received data:" + json.dumps(data) + "\n") # print(data.decode("utf8")) if data['command'] == "shutdown": print(dancerID, ' Received shutdown signal\n') break elif data['command'] == "clocksync": self.respondClockSync(data['message'], dancerID, timerecv) elif data['command'] == "offset": self.clockSyncResponseLock[dancerID].set() self.updateOffset(data['message'], dancerID) elif data['command'] == "timestamp": self.moveCompletedFlag.clear() self.updateTimeStamp(data['message'], dancerID) self.currentMoveReceived[dancerID] = True # if all(value == True for value in self.currentMoveReceived.values()): # print(f"Sync delay calculated:", {self.calculateSyncDelay()}) # self.currentMoveReceived = {key: False for key in self.currentMoveReceived.keys()} elif data['command'] == "data": data.pop('command') self.addData(dancerID, data) elif data['command'] == "moveComplete": pass # self.moveCompletedFlag.clear() except UnicodeDecodeError: print("Packet incorrectly received") pass except Exception as e: print("[ERROR][", dancerID, "] -> ", e) print(self.dancerDataDict[dancerID].qsize()) pass # decrypted_msg = encryptionHandler.decrypt_message(data) print(dancerID, " RETURNING\n") return def handleClockSync(self, dancerID): while True: if self.globalShutDown.is_set(): return self.doClockSync.wait() for _ in range(10): self.broadcastMessage('sync') self.clockSyncResponseLock[dancerID].clear() self.clockSyncResponseLock[dancerID].wait() self.doClockSync.clear() # Check if variance between 10 offsets in dancerID is too high. # If so, force another 10 updates with the specific dancerID def checkOffsetVar(self, dancerID): conn, addr = self.clients[dancerID] self.updateAvgOffset() varLast10 = variance(self.last10Offsets[dancerID]) print("VARIANCE FOR DANCER: ", dancerID, varLast10) if varLast10 > 1e-05: print("Offset variance too high: ", "varLast10", "Resyncing for Dancer: ", dancerID) conn.send(self.encryptionHandler.encrypt_msg("sync")) return def updateOffset(self, message: str, dancerID): # self.offsetLock.acquire() # print(f"{dancerID} has received offsetlock") self.last10Offsets[dancerID][ self.currIndexClockOffset[dancerID]] = float(message) self.currIndexClockOffset[dancerID] = ( self.currIndexClockOffset[dancerID] - 1) % 10 # print(f"{dancerID} is releasing offsetlock") # self.offsetLock.release() if self.clocksyncCount[dancerID] != 10: self.clocksyncCount[dancerID] += 1 if self.clocksyncCount[dancerID] == 10: self.checkOffsetVar(dancerID) self.clocksyncCount[dancerID] = 0 print("Updating dancer " + str(dancerID) + " offset to: " + message + "\n") return def broadcastMessage(self, message): print("BROADCASTING: ", message) message = self.encryptionHandler.encrypt_msg(message) for conn, addr in self.clients.values(): conn.send(message) def respondClockSync(self, message: str, dancerID, timerecv): print(f"Received clock sync request from dancer, {dancerID}") timestamp = message print(f"t1 =", {timestamp}) conn, addr = self.clients[dancerID] # response = str(timerecv) + "|" + str(time.time()) response = json.dumps({ 'command': 'clocksync', 'message': str(timerecv) + '|' + str(time.time()) }) conn.send(self.encryptionHandler.encrypt_msg(response)) def updateAvgOffset(self): for dancerID, offsetList in self.last10Offsets.items(): currSum = 0 numOffsets = 10 for offset in offsetList: if offset is None: numOffsets -= 1 continue currSum += offset if numOffsets == 0: continue self.currAvgOffsets[dancerID] = currSum / numOffsets
import socket from base64 import b64decode, b64encode from Cryptodome.Cipher import AES import time from Util.encryption import EncryptionHandler import json eH = EncryptionHandler(b'Sixteen byte key') host, port = ('127.0.0.1', 10022) mySocket = socket.socket() mySocket.bind((host, port)) mySocket.listen() conn, addr = mySocket.accept() conn.send(eH.encrypt_msg("sync")) data = conn.recv(4096) timeRecv = time.time() time.sleep(1) response = json.dumps({ 'command': 'clocksync', 'message': str(timeRecv) + '|' + str(time.time()) }) conn.send(eH.encrypt_msg(response)) data = conn.recv(4096) timeRecv = time.time() print(eH.decrypt_message(data)) time.sleep(10) conn.send(eH.encrypt_msg("start"))