import time import errno import random import socket import struct import logging import collections import selectors2 as selectors import ConfigParser from contextlib import closing import os,sys,inspect current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) parent_dir = os.path.dirname(current_dir) sys.path.insert(0, parent_dir) import messaging_lib as messaging random.seed() logging.basicConfig( # TODO all prints as logs level=logging.DEBUG, # INFO format="%(asctime)s [%(name)-7.7s] [%(threadName)-12.12s] [%(levelname)-5.5s] %(message)s", handlers=[ logging.FileHandler("client_logs.log"), logging.StreamHandler() ]) class Client: def __init__(self, config_path="client_config.ini"): self.selector = selectors.DefaultSelector() self.client_socket = None self.server_connection = messaging.ConnectionManager() self.server_host = None self.server_port = None self.broadcast_port = None self.connected = False self.client_id = None # Init configs self.config_path = config_path self.config = ConfigParser.ConfigParser() self.load_config() def load_config(self): self.config.read(self.config_path) self.broadcast_port = self.config.getint('SERVER', 'broadcast_port') self.server_port = self.config.getint('SERVER', 'port') self.server_host = self.config.get('SERVER', 'host') self.BUFFER_SIZE = self.config.getint('SERVER', 'buffer_size') self.USE_NTP = self.config.getboolean('NTP', 'use_ntp') self.NTP_HOST = self.config.get('NTP', 'host') self.NTP_PORT = self.config.getint('NTP', 'port') files_directory = self.config.get('FILETRANSFER', 'files_directory') #FRAME_ID = self.config.get('COPTERS', 'frame_id') # TODO in play_animation #self.TAKEOFF_HEIGHT = self.config.getfloat('COPTERS', 'takeoff_height') #self.TAKEOFF_TIME = self.config.getfloat('COPTERS', 'takeoff_time') #self.RFP_TIME = self.config.getfloat('COPTERS', 'reach_first_point_time') #self.SAFE_TAKEOFF = self.config.getboolean('COPTERS', 'safe_takeoff') #self.X0_COMMON = self.config.getfloat('COPTERS', 'x0_common') #self.Y0_COMMON = self.config.getfloat('COPTERS', 'y0_common') #self.X0 = self.config.getfloat('PRIVATE', 'x0') #self.Y0 = self.config.getfloat('PRIVATE', 'y0') #self.USE_LEDS = config.getboolean('PRIVATE', 'use_leds') #play_animation.USE_LEDS = USE_LEDS # TODO in copter_client self.client_id = self.config.get('PRIVATE', 'id') if self.client_id == 'default': self.client_id = 'copter' + str(random.randrange(9999)).zfill(4) #write_to_config('PRIVATE', 'id', client_id) elif self.client_id == '/hostname': self.client_id = socket.gethostname() @staticmethod def get_ntp_time(ntp_host, ntp_port): NTP_PACKET_FORMAT = "!12I" NTP_DELTA = 2208988800L # 1970-01-01 00:00:00 NTP_QUERY = '\x1b' + 47 * '\0' with closing(socket.socket(socket.AF_INET, socket.SOCK_DGRAM)) as s: s.sendto(bytes(NTP_QUERY), (ntp_host, ntp_port)) msg, address = s.recvfrom(1024) unpacked = struct.unpack(NTP_PACKET_FORMAT, msg[0:struct.calcsize(NTP_PACKET_FORMAT)]) return unpacked[10] + float(unpacked[11]) / 2 ** 32 - NTP_DELTA def reconnect(self, timeout=2, attempt_limit=5): logging.info("Trying to connect to {}:{} ...".format(self.server_host, self.server_port)) attempt_count = 0 while not self.connected: logging.info("Waiting for connection, attempt {}".format(attempt_count)) try: self.client_socket = socket.socket() self.client_socket.settimeout(timeout) self.client_socket.connect((self.server_host, self.server_port)) except socket.error as error: if error.errno != errno.EINTR: logging.warning("Can not connect due error: {}".format(error)) attempt_count += 1 time.sleep(timeout) else: logging.critical("Shutting down on keyboard interrupt") raise KeyboardInterrupt else: logging.info("Connection to server successful!") self._connect() break if attempt_count >= attempt_limit: logging.info("Too many attempts. Trying to get new server IP") self.broadcast_bind() attempt_count = 0 def _connect(self): self.connected = True self.client_socket.setblocking(False) events = selectors.EVENT_READ | selectors.EVENT_WRITE self.selector.register(self.client_socket, events, data=self.server_connection) self.server_connection.connect(self.selector, self.client_socket, (self.server_host, self.server_port)) def broadcast_bind(self): broadcast_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) broadcast_client.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) broadcast_client.bind(("", self.broadcast_port)) try: while True: data, addr = broadcast_client.recvfrom(self.BUFFER_SIZE) message = messaging.MessageManager() message.income_raw = data message.process_message() if message.content: logging.info("Received broadcast message {} from {}".format(message.content, addr)) if message.content["command"] == "server_ip": args = message.content["args"] self.server_host = args["host"] self.server_port = int(args["port"]) logging.info("Binding to new IP: {}:{}".format(self.server_host, self.server_port)) #write_to_config("SERVER", "port", port) #write_to_config("SERVER", "host", host) # TODO break finally: broadcast_client.close() def mainloop(self): try: while True: events = self.selector.select(timeout=1) if events: for key, mask in events: if key.data is None: pass else: connection = key.data connection.process_events(mask) if not self.selector.get_map(): logging.warning("No active connections left!") #self.reconnect() except (KeyboardInterrupt, errno.EINTR): logging.critical("Caught interrupt, exiting!") finally: self.selector.close() # TODO class connection if __name__ == "__main__": client = Client() client.reconnect() client.mainloop()