From 3798a5fddcea462bf0011dad7d322aff4cccf592 Mon Sep 17 00:00:00 2001 From: Artem30801 Date: Sun, 5 Jan 2020 17:58:04 +0300 Subject: [PATCH] Client with new config system --- Drone/client.py | 124 ++++++++++++++++++-------------------------- Server/server_qt.py | 2 +- 2 files changed, 52 insertions(+), 74 deletions(-) diff --git a/Drone/client.py b/Drone/client.py index 391d7eb..236c098 100644 --- a/Drone/client.py +++ b/Drone/client.py @@ -1,18 +1,16 @@ import os +import sys import time import errno import random import socket import struct import logging -import collections -import ConfigParser import selectors2 as selectors -import threading from contextlib import closing -import os,sys,inspect # Add parent dir to PATH to import messaging_lib +import inspect # Add parent dir to PATH to import messaging_lib 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) @@ -20,69 +18,40 @@ sys.path.insert(0, parent_dir) logger = logging.getLogger(__name__) import messaging_lib as messaging -import config +from config import ConfigManager -ConfigOption = collections.namedtuple("ConfigOption", ["section", "option", "value"]) +active_client = None # needs to be refactored: Singleton \ factory callbacks -active_client = None # maybe needs to be refactored class Client(object): - def __init__(self, config_path="client_config.ini"): + def __init__(self, config_path="config/client.ini"): self.selector = selectors.DefaultSelector() self.client_socket = None self.server_connection = messaging.ConnectionManager("pi") - self.server_host = None - self.server_port = None - self.broadcast_port = None - self.connected = False self.client_id = None # Init configs + self.config = ConfigManager() self.config_path = config_path - self.config = ConfigParser.ConfigParser() - self.load_config() global active_client active_client = self - # self._last_ping_time = 0 - def load_config(self): - self.config.read(self.config_path) + self.config.load_config_and_spec(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') - - self.client_id = self.config.get('PRIVATE', 'id') - if self.client_id == '/default': + config_id = self.config.private_id.lower() + if config_id == '/default': self.client_id = 'copter' + str(random.randrange(9999)).zfill(4) - self.write_config(False, ConfigOption('PRIVATE', 'id', self.client_id)) - elif self.client_id == '/hostname': + self.config.set('PRIVATE', 'id', self.client_id, write=True) # set and write + elif config_id == '/hostname': self.client_id = socket.gethostname() - elif self.client_id == '/ip': + elif config_id == '/ip': self.client_id = messaging.get_ip_address() - def rewrite_config(self): - with open(self.config_path, 'w') as file: - self.config.write(file) - os.system("chown -R pi:pi /home/pi/clever-show") - - def write_config(self, reload_config=True, *config_options): - for config_option in config_options: - self.config.set(config_option.section, config_option.option, config_option.value) - self.rewrite_config() - - if reload_config: - self.load_config() - @staticmethod def get_ntp_time(ntp_host, ntp_port): NTP_PACKET_FORMAT = "!12I" @@ -96,13 +65,15 @@ class Client(object): return unpacked[10] + float(unpacked[11]) / 2 ** 32 - NTP_DELTA def time_now(self): - if self.USE_NTP: - timenow = self.get_ntp_time(self.NTP_HOST, self.NTP_PORT) + if self.config.ntp_use: + timenow = self.get_ntp_time(self.config.ntp_host, self.config.ntp_port) else: timenow = time.time() return timenow def start(self): + self.load_config() + logger.info("Starting client") messaging.NotifierSock().init(self.selector) @@ -115,8 +86,8 @@ class Client(object): logger.critical("Caught interrupt, exiting!") self.selector.close() - def _reconnect(self, timeout=2.0, attempt_limit=3): - logger.info("Trying to connect to {}:{} ...".format(self.server_host, self.server_port)) + def _reconnect(self, timeout=2.0, attempt_limit=3): # TODO reconnecting broadcast listener in another thread + logger.info("Trying to connect to {}:{} ...".format(self.config.server_host, self.config.server_port)) attempt_count = 0 while not self.connected: logger.info("Waiting for connection, attempt {}".format(attempt_count)) @@ -125,7 +96,7 @@ class Client(object): self.client_socket.settimeout(timeout) self.client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) self.client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - self.client_socket.connect((self.server_host, self.server_port)) + self.client_socket.connect((self.config.server_host, self.config.server_port)) except socket.error as error: if isinstance(error, OSError): if error.errno == errno.EINTR: @@ -149,21 +120,25 @@ class Client(object): 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)) + self.selector.register(self.client_socket, selectors.EVENT_READ, data=self.server_connection) + self.server_connection.connect(self.selector, self.client_socket, + (self.config.server_host, self.config.server_port)) def broadcast_bind(self, timeout=2.0, attempt_limit=3): 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)) broadcast_client.settimeout(timeout) + try: + broadcast_client.bind(("", self.config.broadcast_port)) + except socket.error as error: + logger.error("Error during broadcast listening binding: {}".format(error)) + return attempt_count = 0 try: while attempt_count <= attempt_limit: try: - data, addr = broadcast_client.recvfrom(self.BUFFER_SIZE) + data, addr = broadcast_client.recvfrom(self.config.server_buffer_size) except socket.error as error: logger.warning("Could not receive broadcast due error: {}".format(error)) attempt_count += 1 @@ -175,33 +150,27 @@ class Client(object): logger.info("Received broadcast message {} from {}".format(message.content, addr)) if message.content["command"] == "server_ip": args = message.content["args"] - self.server_port = int(args["port"]) - self.server_host = args["host"] - self.write_config(False, - ConfigOption("SERVER", "port", self.server_port), - ConfigOption("SERVER", "host", self.server_host)) - logger.info("Binding to new IP: {}:{}".format(self.server_host, self.server_port)) + self.config.set("SERVER", "port", int(args["port"])) + self.config.set("SERVER", "host", args["host"]) + self.config.write() + + logger.info("Binding to new IP: {}:{}".format( + self.config.server_host, self.config.server_port)) self.on_broadcast_bind() break finally: broadcast_client.close() - def on_broadcast_bind(self): + def on_broadcast_bind(self): # TODO move ALL binding code here pass def _process_connections(self): while True: events = self.selector.select(timeout=1) - # if time.time() - self._last_ping_time > 5: - # self.server_connection.send_message("ping") - # self._last_ping_time = time.time() - # logging.debug("tick") for key, mask in events: connection = key.data - if connection is None: - pass - else: + if connection is not None: try: connection.process_events(mask) @@ -228,19 +197,28 @@ class Client(object): return -@messaging.message_callback("config_write") +@messaging.message_callback("config") def _command_config_write(*args, **kwargs): - options = [ConfigOption(**raw_option) for raw_option in kwargs["options"]] - logger.info("Writing config_attrs options: {}".format(options)) - active_client.write_config(kwargs["reload"], *options) + print(kwargs) + mode = kwargs.get("mode", "modify") + # exceptions would be risen in case of incorrect config + if mode == "rewrite": + active_client.config.load_from_dict(kwargs["config"], path=active_client.config_path) # with validation + elif mode == "modify": + new_config = ConfigManager() + new_config.load_from_dict(kwargs["config"]) + active_client.config.merge(new_config, validate=True) + + active_client.config.write() + active_client.load_config() @messaging.request_callback("id") def _response_id(*args, **kwargs): new_id = kwargs.get("new_id", None) if new_id is not None: - cfg = ConfigOption("PRIVATE", "id", new_id) - active_client.write_config(True, cfg) + active_client.config.set("PRIVATE", "id", new_id, True) + active_client.load_config() return active_client.client_id diff --git a/Server/server_qt.py b/Server/server_qt.py index ab5b55d..ef33c67 100644 --- a/Server/server_qt.py +++ b/Server/server_qt.py @@ -431,7 +431,7 @@ class MainWindow(QtWidgets.QMainWindow): if not ok or not mode: return - path = QFileDialog.getOpenFileName(self, "Select configuration file", filter="Configs (*.ini *.txt .cfg)")[0] + path = QFileDialog.getOpenFileName(self, "Select configuration file", filter="Configs (*.ini *.txt *.cfg)")[0] if not path: return