Files
clever-show/Server/server_qt.py
Artem30801 f2c674ddf5 Fixes!
2019-03-21 21:23:24 +03:00

439 lines
15 KiB
Python

from PyQt5 import QtWidgets
from PyQt5.QtGui import QStandardItem
from PyQt5.QtGui import QStandardItemModel
from PyQt5.QtCore import QModelIndex
from PyQt5.QtCore import Qt
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QFileDialog
# Importing gui form
from server_gui import Ui_MainWindow
import os
import sys
import glob
import time
import json
import struct
import socket
import random
import threading
import collections
import configparser
# All imports sorted in pyramid
# Functions
def get_ip_address():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
my_ip = s.getsockname()[0]
s.close()
return my_ip
def auto_connect():
while True:
ServerSocket.listen(1)
c, addr = ServerSocket.accept()
print("Got connection from:", str(addr))
if not any(client_addr == addr[0] for client_addr in Client.clients.keys()):
client = Client(addr[0])
print("New client")
else:
print("Reconnected client")
Client.clients[addr[0]].connect(c, addr)
def ip_broadcast(server_ip, server_port):
msg = bytes(Client.form_message(
"server_ip", {"host": server_ip, "port": str(server_port), "id": SERVER_TID}
), "UTF-8")
broadcast_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
broadcast_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
broadcast_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
while True:
time.sleep(10)
broadcast_sock.sendto(msg, ('255.255.255.255', broadcast_port))
print("Broadcast sent")
def broadcast_listener():
broadcast_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
broadcast_client.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
try:
broadcast_client.bind(("", broadcast_port))
except OSError:
print("Another server is running on this computer, shutting down!")
# error_dialog = QtWidgets.QErrorMessage()
# error_dialog.setModal(True)
# error_dialog.showMessage('Another server detected on network, shutting down!')
# error_dialog.exec_()
sys.exit()
while True:
data, addr = broadcast_client.recvfrom(1024)
command, args = Client.parse_message(data.decode("UTF-8"))
if command == "server_ip":
if args["id"] != SERVER_TID:
print("Another server detected on network, shutting down")
# error_dialog = QtWidgets.QErrorMessage()
# error_dialog.setModal(True)
# error_dialog.showMessage('Another server detected on network, shutting down!')
# error_dialog.exec_()
sys.exit()
NTP_DELTA = 2208988800 # 1970-01-01 00:00:00
NTP_QUERY = b'\x1b' + bytes(47)
def get_ntp_time(ntp_host, ntp_port):
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.sendto(NTP_QUERY, (ntp_host, ntp_port))
msg, _ = s.recvfrom(1024)
return int.from_bytes(msg[-8:], 'big') / 2 ** 32 - NTP_DELTA
def requires_connect(f):
def wrapper(*args, **kwargs):
if args[0].connected:
return f(*args, **kwargs)
else:
print("Function requires client to be connected!")
return wrapper
class Client:
clients = {}
def __init__(self, ip):
self.socket = None
self.addr = None
self._send_queue = collections.deque()
self._received_queue = collections.deque()
self._request_queue = collections.OrderedDict()
self.copter_id = None
self.malfunction = False
Client.clients[ip] = self
self.connected = False
def connect(self, client_socket, client_addr):
print("Client connected")
# self._send_queue = collections.deque() # comment for resuming queue after reconnection
self.socket = client_socket
self.addr = client_addr
self.socket.setblocking(0)
self.connected = True
client_thread = threading.Thread(target=self._run, args=())
client_thread.start()
if self.copter_id is None:
self.copter_id = self.get_response("id")
print("Got copter id:", self.copter_id)
model.appendRow((QStandardItem(self.copter_id), )) # TODO: get responses for another columns
def _send_all(self, msg):
self.socket.sendall(struct.pack('>I', len(msg)) + msg)
def _receive_all(self, n):
data = b''
while len(data) < n:
packet = self.socket.recv(min(n - len(data), BUFFER_SIZE))
if not packet:
return None
data += packet
return data
def _receive_message(self):
raw_msglen = self._receive_all(4)
if not raw_msglen:
return None
msglen = struct.unpack('>I', raw_msglen)[0]
msg = self._receive_all(msglen)
return msg
def _run(self):
while self.connected:
try:
if self._send_queue:
msg = self._send_queue.popleft()
print("Send", msg, "to", self.addr)
try:
self._send_all(msg)
except socket.error as e:
print("Attempt to send failed")
self._send_queue.appendleft(msg)
raise e
else:
msg = "ping"
# self._send_all(msg)
try: # check if data in buffer
check = self.socket.recv(BUFFER_SIZE, socket.MSG_PEEK)
if check:
received = self._receive_message()
if received:
received = received.decode("UTF-8")
print("Received", received, "from", self.addr)
command, args = Client.parse_message(received)
if command == "response":
for key, value in self._request_queue.items():
if not value:
self._request_queue[key] = args['value']
print("Request successfully closed")
break
else:
self._received_queue.appendleft(received)
except socket.error:
pass
except socket.error as e:
print("Client error: {}, disconnected".format(e))
self.connected = False
self.socket.close()
break
# time.sleep(0.05)
@staticmethod
def form_message(command: str, dict_arguments: dict = None):
if dict_arguments is None:
dict_arguments = {}
msg_dict = {command: str(dict_arguments).replace(",", '').replace("'", '')[1:-1]}
msg = json.dumps(msg_dict)
return msg
@staticmethod
def parse_message(msg):
try:
j_message = json.loads(msg)
except json.decoder.JSONDecodeError:
print("Json string not in correct format")
return None
str_command = list(j_message.keys())[0]
arguments = list(j_message.values())[0].replace(":", '').split()
dict_arguments = collections.OrderedDict(zip(arguments[::2], arguments[1::2]))
return str_command, dict_arguments
@requires_connect
def send(self, *messages):
for message in messages:
self._send_queue.append(bytes(message, "UTF-8"))
@requires_connect
def get_response(self, requested_value):
self._request_queue[requested_value] = ""
self.send(Client.form_message("request", {"value": requested_value}))
while not self._request_queue[requested_value]:
pass
return self._request_queue.pop(requested_value)
@staticmethod
def send_to_selected(*messages):
if Client.clients:
for client in Client.clients.values(): # TODO change to selected
client.send(*messages)
else:
print("No clients were connected!")
@staticmethod
def request_to_selected(requested_value):
if Client.clients:
for client in Client.clients.values(): # TODO change to selected
client.get_response(requested_value)
else:
print("No clients were connected!")
@staticmethod
def broadcast(message, force_all=False):
if Client.clients:
for client in Client.clients.values():
if (not client.malfunction) or force_all:
client.send(message)
else:
print("No clients were connected!")
@requires_connect
def send_file(self, filepath, dest_filename):
print("Sending file ", dest_filename)
self.send(Client.form_message("writefile", {"filename": dest_filename}))
file = open(filepath, 'rb')
chunk = file.read(BUFFER_SIZE)
while chunk:
self._send_queue.append(chunk) # TODO os.sendfile
chunk = file.read(BUFFER_SIZE)
file.close()
self.send(Client.form_message("/endoffile"))
print("File sent")
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.initUI()
self.show()
def initUI(self):
#Connecting
self.ui.check_button.clicked.connect(self.check_selected)
self.ui.start_button.clicked.connect(self.send_starttime)
self.ui.pause_button.clicked.connect(self.pause_all)
self.ui.stop_button.clicked.connect(self.stop_all)
self.ui.takeoff_button.clicked.connect(self.takeoff_selected)
self.ui.land_button.clicked.connect(self.land_all)
self.ui.disarm_button.clicked.connect(self.disarm_all)
self.ui.action_send_animations.triggered.connect(self.send_animations)
self.ui.action_send_configurations.triggered.connect(self.send_configurations)
#Initing table and table model
self.ui.tableView.setModel(model)
self.ui.tableView.horizontalHeader().setStretchLastSection(True)
@pyqtSlot()
def check_selected(self):
#Client.request_to_selected("selfcheck")
self.ui.start_button.setEnabled(True)
self.ui.takeoff_button.setEnabled(True)
@pyqtSlot()
def send_starttime(self):
dt = self.ui.start_delay_spin.value()
if USE_NTP:
timenow = get_ntp_time(NTP_HOST, NTP_PORT)
else:
timenow = time.time()
print('Now:', time.ctime(timenow), "+ dt =", dt)
Client.send_to_selected(Client.form_message("starttime", {"time": str(timenow + dt)}))
@pyqtSlot()
def stop_all(self):
Client.broadcast(Client.form_message("stop"))
@pyqtSlot()
def pause_all(self):
if self.ui.pause_button.text() == 'Pause':
Client.broadcast(Client.form_message('pause'))
self.ui.pause_button.setText('Resume')
else:
Client.broadcast(Client.form_message('resume'))
self.ui.pause_button.setText('Pause')
@pyqtSlot()
def takeoff_selected(self):
Client.send_to_selected(Client.form_message("takeoff"))
@pyqtSlot()
def land_all(self):
Client.broadcast(Client.form_message("land"))
@pyqtSlot()
def disarm_all(self):
Client.broadcast(Client.form_message("disarm"))
@pyqtSlot()
def send_animations(self):
path = str(QFileDialog.getExistingDirectory(self, "Select Animation Directory"))
if path:
print("Selected directory:", path)
files = [file for file in glob.glob(path + '/*.csv')]
names = [os.path.basename(file).split(".")[0] for file in files]
print(files)
for file, name in zip(files, names):
for copter in Client.clients.values():
if name == copter.copter_id:
copter.send_file(file, "animation.csv") # TODO config
else:
print("Filename not matches with any drone connected")
@pyqtSlot()
def send_configurations(self):
path = QFileDialog.getOpenFileName(self, "Select configuration file", filter="Configs (*.ini *.txt .cfg)")[0]
if path:
print("Selected file:", path)
sendable_config = configparser.ConfigParser()
sendable_config.read(path)
for section in sendable_config.sections():
for option in dict(sendable_config.items(section)):
value = sendable_config[section][option]
print("Got item from config:", section, option, value)
Client.send_to_selected(
Client.form_message('config_write', {'section': section, 'option': option, 'value': value})
)
Client.send_to_selected(Client.form_message("config_reload"))
model = QStandardItemModel()
model.setHorizontalHeaderLabels(
('copter ID', 'animation ID', 'battery V', 'battery %', 'selfcheck', 'time UTC')
)
model.setColumnCount(6)
model.setRowCount(0)
# Pre-initialization
# reading config
config = configparser.ConfigParser()
config.read("server_config.ini")
port = int(config['SERVER']['port'])
broadcast_port = int(config['SERVER']['broadcast_port'])
BUFFER_SIZE = int(config['SERVER']['buffer_size'])
USE_NTP = config.getboolean('NTP', 'use_ntp')
NTP_HOST = config['NTP']['host']
NTP_PORT = int(config['NTP']['port'])
random.seed()
SERVER_TID = str(random.randint(0, 9999)).zfill(4)
ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ServerSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
host = socket.gethostname()
ip = get_ip_address()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
print('Server started on', host, ip, ":", port)
if USE_NTP:
print("Using NTP time:")
now = get_ntp_time(NTP_HOST, NTP_PORT)
else:
now = time.time()
print('Now:', time.ctime(now))
print('Waiting for clients...')
ServerSocket.bind((ip, port))
autoconnect_thread = threading.Thread(target=auto_connect)
autoconnect_thread.daemon = True
autoconnect_thread.start()
broadcast_thread = threading.Thread(target=ip_broadcast, args=(ip, port,))
broadcast_thread.daemon = True
broadcast_thread.start()
listener_thread = threading.Thread(target=broadcast_listener)
listener_thread.daemon = True
#listener_thread.start()
sys.exit(app.exec_())