import globals as g from User_Interface import ui_print import cage_func as cage_controls from threading import Thread import socket import numpy as np # --- Definition of TCP interface --- # # Clients should by default initialize a TCP connection to port 6677 # The commands shown must be terminated with a single \n (newline) char # Commands may be split across multiple packets. # Before useful commands can be sent, declare_api_version must be called. # # A description of the TCP api (safety limits are always enforced): # # set_raw_field [X comp.] [Y comp.] [Z comp.] # Returns: 0 or 1 for success # Accepts decimal point formatted floats, with or without scientific notation. The float() cast must understand it. # The field units are Tesla # This causes an additional field of the given strength to be generated, without regard for the pre-existing # geomagnetic/external fields. # # set_compensated_field [X comp.] [Y comp.] [Z comp.] # Returns: 0 or 1 for success # Accepts decimal point formatted floats, with or without scientific notation. The float() cast must understand it. # The field units are Tesla # This causes a field of exactly the given magnitude to be generated by compensating external factors such as the # geomagnetic field. # # set_coil_currents [X comp.] [Y comp.] [Z comp.] # Returns: 0 or 1 for success # Accepts decimal point formatted floats, with or without scientific notation. The float() cast must understand it. # The field units are Ampere # This establishes the requested current in the individual coils. # # get_api_version # Returns: a string uniquely identifying each API version. # This function can be called before declare_api_version. # Please dont put # # declare_api_version [version] # Returns: 0 or 1 (terminated with newline) # Declare the api version the client application was programmed for. It must be compatible with the current # API version. This prevents unexpected behaviour by forcing programmers to specify which API they are expecting. # This function must be called before sending HW commands. SOCKET_INTERFACE_API_VERSION = "1" class ClientConnectionThread(Thread): def __init__(self, client_socket, address): Thread.__init__(self) self.client_socket = client_socket self.client_address = address self.api_compat = False # Indicates whether the client has a compatible API version def run(self): msg = '' while True: raw_msg = self.client_socket.recv(2048).decode() for char in raw_msg: if char == '\n': msg = msg.rstrip() # Some systems will try to send \r characters... looking at you windows O_O try: response = self.handle_msg(msg) except Exception as e: ui_print("An error occurred while processing a client message") ui_print("Msg: {}".format(msg)) ui_print(e) response = "err" self.client_socket.sendall((response + '\n').encode('utf-8')) msg = '' else: msg += char def handle_msg(self, message): """ Executes command logic and returns string response (for client). """ tokens = message.split(" ") if tokens[0] == "get_api_version": return SOCKET_INTERFACE_API_VERSION elif tokens[0] == "declare_api_version": if tokens[1] == SOCKET_INTERFACE_API_VERSION: self.api_compat = True return "1" else: ui_print("Declared socket API version ({}) is incompatible with current version ({})!".format(tokens[1], SOCKET_INTERFACE_API_VERSION)) return "0" else: # api_compat indicates we have checked the api version and are ready to accept commands if self.api_compat: if tokens[0] == "set_raw_field": x = float(tokens[1]) y = float(tokens[2]) z = float(tokens[3]) field_vec = np.array([x, y, z], dtype=np.float32) # uncompensated cage_controls.set_field_simple(field_vec) return "1" elif tokens[0] == "set_compensated_field": x = float(tokens[1]) y = float(tokens[2]) z = float(tokens[3]) field_vec = np.array([x, y, z], dtype=np.float32) # compensated cage_controls.set_field(field_vec) return "1" elif tokens[0] == "set_coil_currents": x = float(tokens[1]) y = float(tokens[2]) z = float(tokens[3]) current_vec = np.array([x, y, z], dtype=np.float32) cage_controls.set_current_vec(current_vec) return "1" else: # The message given is unknown. The programmer probably did not intend for this, so display an error # even if is not inherently problematic. raise Exception("The command '{}' is unknown".format(tokens[0])) else: raise Exception("The command '{}' may not be called before 'declare_api_version'".format(tokens[0])) class SocketInterfaceThread(Thread): def __init__(self): Thread.__init__(self) self.server_socket = None # Can throw exception, which should be passed on to the instantiator of this class self.configure_tcp_port() def run(self): while True: (client_socket, address) = self.server_socket.accept() new_thread = ClientConnectionThread(client_socket, address) new_thread.start() ui_print("Accepted connection from {}".format(address)) def configure_tcp_port(self): # Creates and configures the listening port self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server_socket.bind(('', g.SOCKET_PORT)) self.server_socket.listen(5) # Limit to max. 5 simultaneous connections ui_print("Listening for TCP connections on port {}".format(g.SOCKET_PORT))