mirror of
https://github.com/CopterExpress/clever-show.git
synced 2026-05-26 23:19:33 +00:00
178 lines
7.0 KiB
Python
178 lines
7.0 KiB
Python
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()
|