mirror of
https://github.com/CopterExpress/clever-show.git
synced 2026-05-26 15:13:26 +00:00
QT server refactor and improvements
This commit is contained in:
@@ -11,7 +11,7 @@ buffer_size = integer(default=1024)
|
||||
remove_disconnected = boolean(default=False)
|
||||
|
||||
[CHECKS]
|
||||
battery_percentage_min = float(default=50.0, min=0, max=100)
|
||||
battery_min = float(default=50.0, min=0, max=100)
|
||||
# in meters
|
||||
start_pos_delta_max = float(default=1.0, min=0)
|
||||
# in meters
|
||||
|
||||
@@ -543,6 +543,7 @@ class ConfigModel(QtCore.QAbstractItemModel):
|
||||
def dict(self):
|
||||
return self.to_dict()
|
||||
|
||||
|
||||
class ConfigTreeWidget(QTreeView):
|
||||
def __init__(self):
|
||||
QTreeView.__init__(self)
|
||||
|
||||
62
Server/copter_table.py
Normal file
62
Server/copter_table.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import logging
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtWidgets import QTableView, QMessageBox
|
||||
|
||||
from server import Client
|
||||
import copter_table_models as table
|
||||
|
||||
|
||||
class CopterTableWidget(QTableView):
|
||||
def __init__(self, model, data_model=table.CopterData):
|
||||
QTableView.__init__(self)
|
||||
|
||||
self.model = model
|
||||
self._data_model = data_model
|
||||
|
||||
self.proxy_model = table.CopterProxyModel()
|
||||
self.signals = table.SignalManager(self.model)
|
||||
|
||||
self.proxy_model.setSourceModel(self.model)
|
||||
self.proxy_model.setDynamicSortFilter(True)
|
||||
|
||||
# Initiate table and table self.model
|
||||
self.setModel(self.proxy_model)
|
||||
|
||||
# Adjust properties
|
||||
self.resizeColumnsToContents()
|
||||
self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||
self.doubleClicked.connect(self.on_double_click)
|
||||
|
||||
# Some fancy wrappers to simplify syntax
|
||||
|
||||
def add_client(self, **kwargs):
|
||||
self.signals.add_client_signal.emit(self._data_model(**kwargs))
|
||||
|
||||
def remove_client_data(self, row_data):
|
||||
self.signals.remove_client_signal.emit(row_data)
|
||||
|
||||
def update_data(self, row, col, data, role=table.ModelDataRole):
|
||||
self.signals.update_data_signal.emit(row, col, data, role)
|
||||
|
||||
@pyqtSlot(QtCore.QModelIndex)
|
||||
def on_double_click(self, index):
|
||||
col = index.column()
|
||||
if col == 7:
|
||||
data = self.proxy_model.data(index, role=table.ModelDataRole)
|
||||
if data and data != "OK":
|
||||
dialog = QMessageBox()
|
||||
dialog.setIcon(QMessageBox.NoIcon)
|
||||
dialog.setStandardButtons(QMessageBox.Ok)
|
||||
dialog.setWindowTitle("Selfcheck info")
|
||||
dialog.setText("\n".join(data[:10]))
|
||||
dialog.setDetailedText("\n".join(data))
|
||||
dialog.exec()
|
||||
|
||||
# def _selfcheck_shortener(self, data): # TODO!!!
|
||||
# shortened = []
|
||||
# for line in data:
|
||||
# if len(line) > 89:
|
||||
# pass
|
||||
# return shortened
|
||||
@@ -1,5 +1,6 @@
|
||||
import sys
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import math
|
||||
import indexed
|
||||
|
||||
@@ -11,10 +12,100 @@ ModelDataRole = 998
|
||||
ModelStateRole = 999
|
||||
|
||||
|
||||
class ModelChecks:
|
||||
checks_dict = {}
|
||||
takeoff_checklist = (3, 4, 6, 7, 8)
|
||||
|
||||
battery_min = 50.0 # config.getfloat('CHECKS', 'battery_percentage_min')
|
||||
start_pos_delta_max = 1.0 # config.getfloat('CHECKS', 'start_pos_delta_max')
|
||||
time_delta_max = 1.0
|
||||
|
||||
@classmethod
|
||||
def col_check(cls, col):
|
||||
def inner(f):
|
||||
def wrapper(item):
|
||||
if item is not None:
|
||||
return f(item)
|
||||
return None
|
||||
|
||||
cls.checks_dict[col] = wrapper
|
||||
return wrapper
|
||||
|
||||
return inner
|
||||
|
||||
@classmethod
|
||||
def all_checks(cls, copter_item):
|
||||
for col, check in cls.checks_dict.items():
|
||||
if not check(copter_item[col]):
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def takeoff_checks(cls, copter_item):
|
||||
for col in cls.takeoff_checklist:
|
||||
if not cls.checks_dict[col](copter_item[col]):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@ModelChecks.col_check(1)
|
||||
def check_ver(item):
|
||||
return True # TODO git version!
|
||||
|
||||
|
||||
@ModelChecks.col_check(2)
|
||||
def check_anim(item):
|
||||
return str(item) != 'No animation'
|
||||
|
||||
|
||||
@ModelChecks.col_check(3)
|
||||
def check_bat(item):
|
||||
if item == "NO_INFO":
|
||||
return False
|
||||
return item[1]*100 > ModelChecks.battery_min
|
||||
|
||||
|
||||
@ModelChecks.col_check(4)
|
||||
def check_sys_status(item):
|
||||
return item == "STANDBY"
|
||||
|
||||
|
||||
@ModelChecks.col_check(5)
|
||||
def check_cal_status(item):
|
||||
return item == "OK"
|
||||
|
||||
|
||||
@ModelChecks.col_check(6)
|
||||
def check_mode(item):
|
||||
return (item != "NO_FCU") and not ("CMODE" in item)
|
||||
|
||||
|
||||
@ModelChecks.col_check(7)
|
||||
def check_selfcheck(item):
|
||||
return item == "OK"
|
||||
|
||||
|
||||
@ModelChecks.col_check(8)
|
||||
def check_pos_status(item):
|
||||
if item == 'NO_POS':
|
||||
return False
|
||||
return not math.isnan(item[0])
|
||||
|
||||
|
||||
@ModelChecks.col_check(9)
|
||||
def check_start_pos_status(item):
|
||||
return item != 'NO_POS'
|
||||
|
||||
|
||||
@ModelChecks.col_check(10)
|
||||
def check_time_delta(item):
|
||||
return abs(item) < ModelChecks.time_delta_max
|
||||
|
||||
|
||||
class CopterData:
|
||||
class_basic_attrs = indexed.IndexedOrderedDict([('copter_id', None), ('git_ver', None), ('anim_id', None),
|
||||
('battery', None), ('sys_status', None), ('cal_status', None),
|
||||
('mode', None), ('selfcheck', None), ('position', None),
|
||||
('mode', None), ('selfcheck', None), ('position', None),
|
||||
('start_pos', None), ('time_delta', None), ('client', None)])
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
@@ -35,8 +126,9 @@ class StatedCopterData(CopterData):
|
||||
class_basic_states = indexed.IndexedOrderedDict([("checked", 0), ("selfchecked", None), ("takeoff_ready", None),
|
||||
("copter_id", True), ])
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
def __init__(self, checks_class=ModelChecks, **kwargs):
|
||||
self.states = CopterData(**self.class_basic_states)
|
||||
self.checks = ModelChecks
|
||||
|
||||
super(StatedCopterData, self).__init__(**kwargs)
|
||||
|
||||
@@ -46,31 +138,30 @@ class StatedCopterData(CopterData):
|
||||
if key in self.class_basic_attrs.keys():
|
||||
try:
|
||||
self.states.__dict__[key] = \
|
||||
Checks.all_checks[self.attrs_dict.keys().index(key)](value)
|
||||
ModelChecks.checks_dict[self.attrs_dict.keys().index(key)](value)
|
||||
if key == 'start_pos':
|
||||
if (self.__dict__['position'] is not None) and (self.__dict__['start_pos'] is not None):
|
||||
current_pos = get_position(self.__dict__['position'])
|
||||
start_pos = get_position(self.__dict__['start_pos'])
|
||||
delta = get_position_delta(current_pos, start_pos)
|
||||
if delta != 'NO_POS':
|
||||
self.states.__dict__[key] = (delta < Checks.start_pos_delta_max)
|
||||
self.states.__dict__[key] = (delta < ModelChecks.start_pos_delta_max)
|
||||
except KeyError: # No check present for that col
|
||||
pass
|
||||
else: # update selfchecked and takeoff_ready
|
||||
self.states.__dict__["selfchecked"] = all(
|
||||
[self.states[i] for i in Checks.all_checks.keys()]
|
||||
[self.states[i] for i in ModelChecks.checks_dict.keys()]
|
||||
)
|
||||
|
||||
self.states.__dict__["takeoff_ready"] = all(
|
||||
[self.states[i] for i in Checks.takeoff_checklist]
|
||||
[self.states[i] for i in ModelChecks.takeoff_checklist]
|
||||
)
|
||||
|
||||
def get_position(pos_string):
|
||||
pos = []
|
||||
pos_str = pos_string.split(' ')
|
||||
if pos_str[0] != 'nan' and pos_str[0] != 'NO_POS':
|
||||
def get_position(pos_array):
|
||||
if pos_array[0] != 'nan' and pos_array != 'NO_POS':
|
||||
pos = []
|
||||
for i in range(3):
|
||||
pos.append(float(pos_str[i]))
|
||||
pos.append(pos_array[i])
|
||||
else:
|
||||
pos = 'NO_POS'
|
||||
return pos
|
||||
@@ -85,28 +176,122 @@ def get_position_delta(pos1, pos2):
|
||||
return 'NO_POS'
|
||||
|
||||
|
||||
class Checks:
|
||||
all_checks = {}
|
||||
takeoff_checklist = (3, 4, 6, 7, 8)
|
||||
battery_min = 50 # config.getfloat('CHECKS', 'battery_percentage_min')
|
||||
start_pos_delta_max = 1 # config.getfloat('CHECKS', 'start_pos_delta_max')
|
||||
time_delta_max = 1 # config.getfloat('CHECKS', 'time_delta_max')
|
||||
class ModelFormatter:
|
||||
view_formatters = {}
|
||||
place_formatters = {}
|
||||
VIEW_FORMATTER = False
|
||||
PLACE_FORMATTER = True
|
||||
|
||||
@classmethod
|
||||
def format_view(cls, col, value):
|
||||
if col in cls.view_formatters:
|
||||
return cls.view_formatters[col](value)
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def format_place(cls, col, value):
|
||||
if col in cls.place_formatters:
|
||||
return cls.place_formatters[col](value)
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def col_format(cls, col, format_type):
|
||||
def inner(f):
|
||||
if format_type:
|
||||
cls.place_formatters[col] = f
|
||||
else:
|
||||
cls.view_formatters[col] = f
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
@ModelFormatter.col_format(0, ModelFormatter.PLACE_FORMATTER)
|
||||
def place_id(value):
|
||||
value = value.stip()
|
||||
# check user hostname spelling http://man7.org/linux/man-pages/man7/hostname.7.html
|
||||
# '-' (hyphen) not first; latin letters/numbers/hyphens; length form 1 to 63
|
||||
# or matches command pattern
|
||||
if re.match("^(?!-)[A-Za-z0-9-]{1,63}$", value) or re.match("^/[A-Za-z0-9]*$", value):
|
||||
return value
|
||||
else:
|
||||
msgbox = QtWidgets.QMessageBox()
|
||||
msgbox.setWindowTitle("Wrong input for the copter name!")
|
||||
msgbox.setIcon(QtWidgets.QMessageBox.Critical)
|
||||
msgbox.setText(
|
||||
"Wrong input for the copter name!\n"
|
||||
"Please use only A-Z, a-z, 0-9, and '-' chars.\n"
|
||||
"Don't use '-' as first char.")
|
||||
msgbox.exec_()
|
||||
return None
|
||||
|
||||
|
||||
@ModelFormatter.col_format(3, ModelFormatter.PLACE_FORMATTER)
|
||||
def place_battery(value):
|
||||
if isinstance(value, list):
|
||||
battery_v, battery_p = value
|
||||
if math.isnan(battery_v) or math.isnan(battery_p):
|
||||
return "NO_INFO"
|
||||
return value
|
||||
|
||||
|
||||
@ModelFormatter.col_format(3, ModelFormatter.VIEW_FORMATTER)
|
||||
def view_battery(value):
|
||||
if isinstance(value, list):
|
||||
battery_v, battery_p = value
|
||||
return "{:.1f}V {:d}%".format(battery_v, int(battery_p*100))
|
||||
return value
|
||||
|
||||
@ModelFormatter.col_format(7, ModelFormatter.VIEW_FORMATTER)
|
||||
def view_selfcheck(value):
|
||||
if isinstance(value, list):
|
||||
if len(value)==1:
|
||||
return value[0]
|
||||
return "ERROR"
|
||||
return value
|
||||
|
||||
@ModelFormatter.col_format(8, ModelFormatter.VIEW_FORMATTER)
|
||||
def view_selfcheck(value):
|
||||
if isinstance(value, list):
|
||||
x, y, z, yaw, frame = value
|
||||
return "{:.2f} {:.2f} {:.2f} {:d} {}".format(x, y, z, int(yaw), frame)
|
||||
return value
|
||||
|
||||
@ModelFormatter.col_format(9, ModelFormatter.VIEW_FORMATTER)
|
||||
def view_selfcheck(value):
|
||||
if isinstance(value, list):
|
||||
x, y, z = value
|
||||
return "{:.2f} {:.2f} {:.2f}".format(x, y, z)
|
||||
return value
|
||||
|
||||
@ModelFormatter.col_format(10, ModelFormatter.PLACE_FORMATTER)
|
||||
def place_time_delta(value):
|
||||
return abs(value - time.time())
|
||||
|
||||
|
||||
@ModelFormatter.col_format(10, ModelFormatter.VIEW_FORMATTER)
|
||||
def view_time_delta(value):
|
||||
return "{:.3f}".format(value)
|
||||
|
||||
class CopterDataModel(QtCore.QAbstractTableModel):
|
||||
selected_ready_signal = QtCore.pyqtSignal(bool)
|
||||
selected_takeoff_ready_signal = QtCore.pyqtSignal(bool)
|
||||
selected_flip_ready_signal = QtCore.pyqtSignal(bool)
|
||||
selected_flip_ready_signal = QtCore.pyqtSignal(bool) # TODO fix this signals
|
||||
selected_calibrating_signal = QtCore.pyqtSignal(bool)
|
||||
selected_calibration_ready_signal = QtCore.pyqtSignal(bool)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, checks=ModelChecks, formatter=ModelFormatter, parent=None):
|
||||
super(CopterDataModel, self).__init__(parent)
|
||||
self.headers = ('copter ID', 'version', ' animation ID ', ' battery ', ' system ', 'sensors',
|
||||
' mode ', 'checks', 'current x y z yaw frame_id', ' start x y z ', 'dt')
|
||||
' mode ', ' checks ', 'current x y z yaw frame_id', ' start x y z ', 'dt')
|
||||
self.data_contents = []
|
||||
|
||||
self.on_id_changed = None
|
||||
self.checks = checks
|
||||
self.formatter = formatter
|
||||
|
||||
self.first_col_is_checked = False
|
||||
|
||||
@@ -139,15 +324,15 @@ class CopterDataModel(QtCore.QAbstractTableModel):
|
||||
|
||||
def flip_ready(self, contents=()):
|
||||
contents = contents or self.data_contents
|
||||
return filter(lambda x: flip_checks(x), contents) # possibly change as takeoff checks
|
||||
return filter(flip_checks, contents) # possibly change as takeoff checks
|
||||
|
||||
def calibrating(self, contents=()):
|
||||
contents = contents or self.data_contents
|
||||
return filter(lambda x: calibrating_check(x), contents)
|
||||
return filter(calibrating_check, contents)
|
||||
|
||||
def calibration_ready(self, contents=()):
|
||||
contents = contents or self.data_contents
|
||||
return filter(lambda x: calibration_ready_check(x), contents)
|
||||
return filter(calibration_ready_check, contents)
|
||||
|
||||
def get_row_index(self, row_data):
|
||||
try:
|
||||
@@ -180,7 +365,7 @@ class CopterDataModel(QtCore.QAbstractTableModel):
|
||||
col = index.column()
|
||||
if role == Qt.DisplayRole or role == Qt.EditRole: # Separate editRole in case of editing non-text
|
||||
item = self.data_contents[row][col]
|
||||
return str(item) if item is not None else ""
|
||||
return str(self.formatter.format_view(col, item)) if item is not None else ""
|
||||
elif role == ModelDataRole:
|
||||
return self.data_contents[row][col]
|
||||
|
||||
@@ -202,7 +387,7 @@ class CopterDataModel(QtCore.QAbstractTableModel):
|
||||
return self.data_contents[row].states.checked
|
||||
|
||||
if role == QtCore.Qt.TextAlignmentRole and col != 0:
|
||||
return QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter
|
||||
return QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter
|
||||
|
||||
def update_model(self, index=QtCore.QModelIndex(), role=QtCore.Qt.EditRole):
|
||||
selected = set(self.user_selected())
|
||||
@@ -226,19 +411,14 @@ class CopterDataModel(QtCore.QAbstractTableModel):
|
||||
|
||||
if role == Qt.CheckStateRole:
|
||||
self.data_contents[row].states.checked = value
|
||||
elif role == Qt.EditRole: # For user actions with data
|
||||
if col == 0:
|
||||
# check user hostname spelling http://man7.org/linux/man-pages/man7/hostname.7.html
|
||||
if value[0] != '-' and len(value) <= 63 and re.match("^[A-Za-z0-9-]*$", value):
|
||||
self.data_contents[row].client.send_message("id", {"new_id": value})
|
||||
elif role == Qt.EditRole: # For user/outer actions with data, place modifiers applied
|
||||
formatted_value = self.formatter.format_place(col, value)
|
||||
if formatted_value is not None: # todo use new := syntax
|
||||
self.data_contents[row][col] = formatted_value
|
||||
|
||||
if col == 0:
|
||||
self.data_contents[row].client.send_message("id", {"new_id": formatted_value})
|
||||
self.data_contents[row].client.remove()
|
||||
else:
|
||||
msg = QtWidgets.QMessageBox()
|
||||
msg.setIcon(QtWidgets.QMessageBox.Critical)
|
||||
msg.setText("Wrong input for the copter name!\nPlease use only A-Z, a-z, 0-9, and '-' chars.\nDon't use '-' as first char.")
|
||||
msg.exec_()
|
||||
else:
|
||||
self.data_contents[row][col] = value
|
||||
|
||||
elif role == ModelDataRole: # For inner setting\editing of data
|
||||
self.data_contents[row][col] = value
|
||||
@@ -250,7 +430,7 @@ class CopterDataModel(QtCore.QAbstractTableModel):
|
||||
self.update_model(index, role)
|
||||
return True
|
||||
|
||||
def select_all(self):
|
||||
def select_all(self): # probably NOT thread-safe!
|
||||
self.first_col_is_checked = not self.first_col_is_checked
|
||||
for row_num, copter in enumerate(self.data_contents):
|
||||
copter.states.checked = int(self.first_col_is_checked)*2
|
||||
@@ -270,109 +450,23 @@ class CopterDataModel(QtCore.QAbstractTableModel):
|
||||
def add_client(self, client):
|
||||
self.insertRows([client])
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def remove_client(self, row):
|
||||
@QtCore.pyqtSlot(int) # Probably deprecated now
|
||||
def remove_row(self, row):
|
||||
self.removeRows(row)
|
||||
|
||||
|
||||
def col_check(col):
|
||||
def inner(f):
|
||||
Checks.all_checks[col] = f
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
@col_check(1)
|
||||
def check_ver(item):
|
||||
if not item:
|
||||
return None
|
||||
return True
|
||||
|
||||
@col_check(2)
|
||||
def check_anim(item):
|
||||
if not item:
|
||||
return None
|
||||
return str(item) != 'No animation'
|
||||
|
||||
@col_check(3)
|
||||
def check_bat(item):
|
||||
if not item:
|
||||
return None
|
||||
if item == "NO_INFO":
|
||||
return False
|
||||
else:
|
||||
return float(item.split(' ')[1][:-1]) > Checks.battery_min
|
||||
|
||||
@col_check(4)
|
||||
def check_sys_status(item):
|
||||
if not item:
|
||||
return None
|
||||
return item == "STANDBY"
|
||||
|
||||
@col_check(5)
|
||||
def check_cal_status(item):
|
||||
if not item:
|
||||
return None
|
||||
return item == "OK"
|
||||
|
||||
@col_check(6)
|
||||
def check_mode(item):
|
||||
if not item:
|
||||
return None
|
||||
return (item != "NO_FCU") and not ("CMODE" in item)
|
||||
|
||||
@col_check(7)
|
||||
def check_selfcheck(item):
|
||||
if not item:
|
||||
return None
|
||||
return item == "OK"
|
||||
|
||||
@col_check(8)
|
||||
def check_pos_status(item):
|
||||
if not item:
|
||||
return None
|
||||
str_pos = item.split(' ')
|
||||
return str_pos[0] != 'nan' and str_pos[0] != 'NO_POS'
|
||||
|
||||
@col_check(9)
|
||||
def check_start_pos_status(item):
|
||||
if not item:
|
||||
return None
|
||||
str_start_pos = item.split(' ')
|
||||
return str_start_pos[0] != 'nan' and str_start_pos[0] != 'NO_POS'
|
||||
|
||||
@col_check(10)
|
||||
def check_time_delta(item):
|
||||
if not item:
|
||||
return None
|
||||
return abs(float(item)) < Checks.time_delta_max
|
||||
|
||||
|
||||
def all_checks(copter_item):
|
||||
for col, check in Checks.all_checks.items():
|
||||
if not check(copter_item[col]):
|
||||
return False
|
||||
return True
|
||||
|
||||
def takeoff_checks(copter_item):
|
||||
for col in Checks.takeoff_checklist:
|
||||
if not Checks.all_checks[col](copter_item[col]):
|
||||
return False
|
||||
return True
|
||||
@QtCore.pyqtSlot(object)
|
||||
def remove_row_data(self, data):
|
||||
row = self.get_row_index(data)
|
||||
if row is not None:
|
||||
self.removeRows(row)
|
||||
|
||||
def flip_checks(copter_item):
|
||||
for col in Checks.takeoff_checklist:
|
||||
for col in ModelChecks.takeoff_checklist:
|
||||
if col != 4 or col != 7:
|
||||
if not Checks.all_checks[col](copter_item[col]):
|
||||
return False
|
||||
else:
|
||||
if copter_item[4] != "ACTIVE":
|
||||
if not ModelChecks.checks_dict[col](copter_item[col]):
|
||||
return False
|
||||
elif copter_item[4] != "ACTIVE":
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@@ -381,7 +475,7 @@ def calibrating_check(copter_item):
|
||||
|
||||
|
||||
def calibration_ready_check(copter_item):
|
||||
if not Checks.all_checks[4](copter_item[4]):
|
||||
if not ModelChecks.checks_dict[4](copter_item[4]):
|
||||
return False
|
||||
return not calibrating_check(copter_item)
|
||||
|
||||
@@ -408,7 +502,16 @@ class CopterProxyModel(QtCore.QSortFilterProxyModel):
|
||||
class SignalManager(QtCore.QObject):
|
||||
update_data_signal = QtCore.pyqtSignal(int, int, QtCore.QVariant, QtCore.QVariant)
|
||||
add_client_signal = QtCore.pyqtSignal(object)
|
||||
remove_client_signal = QtCore.pyqtSignal(int)
|
||||
remove_row_signal = QtCore.pyqtSignal(int)
|
||||
remove_client_signal = QtCore.pyqtSignal(object)
|
||||
|
||||
def __init__(self, model):
|
||||
super().__init__()
|
||||
|
||||
self.update_data_signal.connect(model.update_item)
|
||||
self.add_client_signal.connect(model.add_client)
|
||||
self.remove_row_signal.connect(model.remove_row)
|
||||
self.remove_client_signal.connect(model.remove_row_data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
BIN
Server/icons/coex_splash.jpg
Normal file
BIN
Server/icons/coex_splash.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
123
Server/server.py
123
Server/server.py
@@ -22,33 +22,23 @@ random.seed()
|
||||
|
||||
now = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
path = 'server_logs'
|
||||
if not os.path.exists(path):
|
||||
log_path = 'server_logs'
|
||||
if not os.path.exists(log_path):
|
||||
try:
|
||||
os.mkdir(path)
|
||||
os.mkdir(log_path)
|
||||
except OSError:
|
||||
print("Creation of the directory %s failed" % path)
|
||||
print("Creation of the directory {} failed".format(log_path))
|
||||
else:
|
||||
print("Successfully created the directory %s " % path)
|
||||
|
||||
logging.basicConfig( # TODO all prints as logs
|
||||
level=logging.DEBUG,
|
||||
format="%(asctime)s [%(name)-7.7s] [%(threadName)-19.19s] [%(levelname)-7.7s] %(message)s",
|
||||
handlers=[
|
||||
logging.FileHandler("server_logs/{}.log".format(now)),
|
||||
logging.StreamHandler()
|
||||
])
|
||||
print("Successfully created the directory {}".format(log_path))
|
||||
|
||||
ConfigOption = collections.namedtuple("ConfigOption", ["section", "option", "value"])
|
||||
|
||||
|
||||
class Server(messaging.Singleton):
|
||||
def __init__(self, server_id=None, config_path="config/server.ini", on_stop=None):
|
||||
def __init__(self, server_id=None, config_path="config/server.ini"):
|
||||
self.id = server_id if server_id else str(random.randint(0, 9999)).zfill(4)
|
||||
self.time_started = 0
|
||||
|
||||
self.on_stop = on_stop
|
||||
|
||||
# Init socket
|
||||
self.sel = selectors.DefaultSelector()
|
||||
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
@@ -70,16 +60,20 @@ class Server(messaging.Singleton):
|
||||
|
||||
self.broadcast_thread = threading.Thread(target=self._ip_broadcast, daemon=True,
|
||||
name='IP broadcast sender')
|
||||
self.broadcast_thread_running = threading.Event()
|
||||
self.broadcast_thread_running = threading.Event() # TOOD replace by interrupt
|
||||
self.broadcast_thread_interrupt = threading.Event()
|
||||
|
||||
self.listener_thread = threading.Thread(target=self._broadcast_listen, daemon=True,
|
||||
name='IP broadcast listener')
|
||||
self.listener_thread_running = threading.Event()
|
||||
|
||||
def start(self): # do_auto_connect=True, , do_listen_broadcast=False
|
||||
# load config on startup
|
||||
def load_config(self):
|
||||
self.config.load_config_and_spec(self.config_path)
|
||||
|
||||
def start(self):
|
||||
# load config on startup
|
||||
self.load_config()
|
||||
|
||||
self.time_started = time.time()
|
||||
|
||||
logging.info("Starting server with id: {} on {}:{} !".format(self.id, self.ip, self.config.server_port))
|
||||
@@ -102,18 +96,26 @@ class Server(messaging.Singleton):
|
||||
|
||||
def stop(self):
|
||||
logging.info("Stopping server")
|
||||
|
||||
self.client_processor_thread_running.clear()
|
||||
|
||||
self.broadcast_thread_interrupt.set()
|
||||
self.broadcast_thread_running.clear()
|
||||
|
||||
self.listener_thread_running.clear()
|
||||
|
||||
messaging.NotifierSock().notify()
|
||||
|
||||
self.server_socket.close()
|
||||
self.sel.close()
|
||||
|
||||
messaging.NotifierSock().close()
|
||||
|
||||
logging.info("Server stopped")
|
||||
|
||||
if self.on_stop is not None:
|
||||
self.on_stop()
|
||||
|
||||
sys.exit("Stopped")
|
||||
def terminate(self, reason="Terminated"):
|
||||
self.stop()
|
||||
logging.critical(reason)
|
||||
|
||||
@staticmethod
|
||||
def get_ntp_time(ntp_host, ntp_port):
|
||||
@@ -126,22 +128,22 @@ class Server(messaging.Singleton):
|
||||
|
||||
def time_now(self):
|
||||
if self.config.ntp_use:
|
||||
timenow = self.get_ntp_time(self.config.ntp_host, self.config.ntp_port)
|
||||
else:
|
||||
timenow = time.time()
|
||||
return timenow
|
||||
return self.get_ntp_time(self.config.ntp_host, self.config.ntp_port)
|
||||
|
||||
return time.time()
|
||||
|
||||
# noinspection PyArgumentList
|
||||
def _client_processor(self):
|
||||
logging.info("Client processor (selector) thread started!")
|
||||
|
||||
messaging.NotifierSock().init(self.sel)
|
||||
|
||||
self.server_socket.listen()
|
||||
self.server_socket.setblocking(False)
|
||||
self.sel.register(self.server_socket, selectors.EVENT_READ, data=None) #| selectors.EVENT_WRITE
|
||||
|
||||
messaging.NotifierSock().bind((self.ip, self.config.server_port))
|
||||
|
||||
while self.client_processor_thread_running.is_set():
|
||||
events = self.sel.select()
|
||||
events = self.sel.select(timeout=1)
|
||||
#logging.error('tick')
|
||||
for key, mask in events:
|
||||
# logging.error(mask)
|
||||
@@ -161,19 +163,21 @@ class Server(messaging.Singleton):
|
||||
logging.info("Client autoconnect thread stopped!")
|
||||
|
||||
def _connect_client(self, sock):
|
||||
conn, addr = sock.accept()
|
||||
try:
|
||||
conn, addr = sock.accept()
|
||||
except OSError:
|
||||
logging.error("Error while connecting socket!")
|
||||
return
|
||||
|
||||
logging.info("Got connection from: {}".format(str(addr)))
|
||||
conn.setblocking(False)
|
||||
|
||||
if addr[0] == self.ip and messaging.NotifierSock().addr is None:
|
||||
client = messaging.NotifierSock()
|
||||
logging.info("Notifier sock client")
|
||||
|
||||
elif not any([client_addr == addr[0] for client_addr in Client.clients.keys()]):
|
||||
if not any([client_addr == addr[0] for client_addr in Client.clients.keys()]):
|
||||
client = Client(addr[0])
|
||||
logging.info("New client")
|
||||
else:
|
||||
client = Client.clients[addr[0]]
|
||||
client.close(True) # to ensure in unregistering
|
||||
logging.info("Reconnected client")
|
||||
self.sel.register(conn, selectors.EVENT_READ, data=client)
|
||||
client.connect(self.sel, conn, addr)
|
||||
@@ -191,6 +195,7 @@ class Server(messaging.Singleton):
|
||||
|
||||
try:
|
||||
while self.broadcast_thread_running.is_set():
|
||||
self.broadcast_thread_interrupt.wait(timeout=self.config.broadcast_delay)
|
||||
broadcast_sock.sendto(msg, ('255.255.255.255', self.config.broadcast_port))
|
||||
logging.debug("Broadcast sent")
|
||||
|
||||
@@ -205,9 +210,8 @@ class Server(messaging.Singleton):
|
||||
try:
|
||||
broadcast_client.bind(("", self.config.broadcast_port))
|
||||
except OSError:
|
||||
logging.critical("Another server is running on this computer, shutting down!")
|
||||
# TODO popup and as function
|
||||
self.stop()
|
||||
self.terminate("Another server is running on this computer, shutting down!")
|
||||
return
|
||||
|
||||
try:
|
||||
while self.listener_thread_running.is_set():
|
||||
@@ -225,9 +229,7 @@ class Server(messaging.Singleton):
|
||||
|
||||
if different_id and self_younger:
|
||||
# younger server should shut down
|
||||
logging.critical("Another server detected over the network, shutting down!")
|
||||
# TODO popup
|
||||
self.stop()
|
||||
self.terminate("Another server detected over the network, shutting down!")
|
||||
|
||||
else:
|
||||
logging.warning("Got wrong broadcast message from {}".format(addr))
|
||||
@@ -268,7 +270,7 @@ class Client(messaging.ConnectionManager):
|
||||
on_disconnect = None
|
||||
|
||||
def __init__(self, ip):
|
||||
super(Client, self).__init__()
|
||||
super().__init__()
|
||||
self.copter_id = None
|
||||
self.connected = False
|
||||
|
||||
@@ -276,7 +278,7 @@ class Client(messaging.ConnectionManager):
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(copter_id):
|
||||
for client in Client.clients.values():
|
||||
for client in Client.clients.values(): # TODO filter
|
||||
if client.copter_id == copter_id:
|
||||
return client
|
||||
|
||||
@@ -285,7 +287,7 @@ class Client(messaging.ConnectionManager):
|
||||
if not self.resume_queue:
|
||||
self._send_queue = collections.deque()
|
||||
|
||||
super(Client, self).connect(client_selector, client_socket, client_addr)
|
||||
super().connect(client_selector, client_socket, client_addr)
|
||||
|
||||
self.connected = True
|
||||
|
||||
@@ -295,10 +297,12 @@ class Client(messaging.ConnectionManager):
|
||||
if self.on_connect:
|
||||
self.on_connect(self)
|
||||
|
||||
def _got_id(self, value):
|
||||
def _got_id(self, _client, value):
|
||||
logging.info("Got copter id: {} for client {}".format(value, self.addr))
|
||||
old_id = self.copter_id
|
||||
self.copter_id = value
|
||||
if self.on_first_connect:
|
||||
|
||||
if old_id is None and self.on_first_connect:
|
||||
self.on_first_connect(self)
|
||||
|
||||
def close(self, inner=False):
|
||||
@@ -308,25 +312,26 @@ class Client(messaging.ConnectionManager):
|
||||
self.on_disconnect(self)
|
||||
|
||||
if inner:
|
||||
super(Client, self)._close()
|
||||
super()._close()
|
||||
else:
|
||||
super(Client, self).close()
|
||||
super().close()
|
||||
|
||||
logging.info("Connection to {} closed!".format(self.copter_id))
|
||||
|
||||
def remove(self):
|
||||
if self.connected:
|
||||
self.close()
|
||||
if self.clients:
|
||||
try:
|
||||
self.clients.pop(self.addr[0])
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
|
||||
try:
|
||||
self.clients.pop(self.addr[0])
|
||||
except KeyError as e:
|
||||
logging.error(e)
|
||||
|
||||
logging.info("Client {} successfully removed!".format(self.copter_id))
|
||||
|
||||
@requires_connect
|
||||
def _send(self, data):
|
||||
super(Client, self)._send(data)
|
||||
super()._send(data)
|
||||
logging.debug("Queued data to send: {}".format(data))
|
||||
|
||||
def send_config_options(self, *options: ConfigOption, reload_config=True):
|
||||
@@ -352,6 +357,14 @@ class Client(messaging.ConnectionManager):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format="%(asctime)s [%(name)-7.7s] [%(threadName)-19.19s] [%(levelname)-7.7s] %(message)s",
|
||||
handlers=[
|
||||
logging.FileHandler("server_logs/{}.log".format(now)),
|
||||
logging.StreamHandler()
|
||||
])
|
||||
|
||||
server = Server()
|
||||
server.start()
|
||||
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
import os
|
||||
import re
|
||||
import glob
|
||||
import math
|
||||
import time
|
||||
import logging
|
||||
import asyncio
|
||||
import threading
|
||||
import functools
|
||||
import itertools
|
||||
from functools import partial
|
||||
|
||||
from PyQt5 import QtWidgets, QtMultimedia
|
||||
from PyQt5.QtGui import QStandardItemModel, QStandardItem
|
||||
from PyQt5.QtGui import QStandardItemModel, QStandardItem, QPixmap
|
||||
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal, QObject, QUrl
|
||||
|
||||
from PyQt5.QtWidgets import QFileDialog, QMessageBox, QApplication, QWidget, QInputDialog, QLineEdit
|
||||
from PyQt5.QtWidgets import QFileDialog, QMessageBox, QApplication, QWidget, QInputDialog, QLineEdit, QStatusBar, \
|
||||
QSplashScreen, QProgressBar
|
||||
from quamash import QEventLoop, QThreadExecutor
|
||||
|
||||
# Importing gui form
|
||||
from server_gui import Ui_MainWindow
|
||||
|
||||
from server import *
|
||||
import config
|
||||
|
||||
from server import Server, Client
|
||||
import messaging_lib as messaging
|
||||
from copter_table_models import *
|
||||
|
||||
import copter_table_models as table
|
||||
from timing_lib import precise_sleep
|
||||
from copter_table import CopterTableWidget
|
||||
from emergency import *
|
||||
|
||||
import threading
|
||||
|
||||
|
||||
def wait(end, interrupter=threading.Event(), maxsleep=0.1):
|
||||
# Added features to interrupter sleep and set max sleeping interval
|
||||
|
||||
while not interrupter.is_set(): # Basic implementation of pause module until()
|
||||
now = time.time()
|
||||
diff = min(end - now, maxsleep)
|
||||
if diff <= 0:
|
||||
break
|
||||
else:
|
||||
time.sleep(diff / 2)
|
||||
def multi_glob(*patterns):
|
||||
return itertools.chain.from_iterable(glob.iglob(pattern) for pattern in patterns)
|
||||
|
||||
|
||||
def confirmation_required(text="Are you sure?", label="Confirm operation?"):
|
||||
def inner(f):
|
||||
|
||||
@functools.wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
reply = QMessageBox.question(
|
||||
@@ -55,41 +55,112 @@ def confirmation_required(text="Are you sure?", label="Confirm operation?"):
|
||||
|
||||
return inner
|
||||
|
||||
# noinspection PyArgumentList,PyCallByClass
|
||||
|
||||
class ExitMsgbox(logging.Handler):
|
||||
def emit(self, record):
|
||||
loop.call_soon_threadsafe(self._emit, record) # TODO replace loop call
|
||||
|
||||
def _emit(self, record):
|
||||
# window.close()
|
||||
QMessageBox.warning(None, "Critical error in {}: {}". format(record.name, record.threadName), record.msg)
|
||||
QtWidgets.QApplication.quit()
|
||||
sys.exit(record.msg)
|
||||
|
||||
|
||||
class ServerQt(Server):
|
||||
def load_config(self):
|
||||
super().load_config()
|
||||
table.ModelChecks.battery_min = self.config.checks_battery_min
|
||||
table.ModelChecks.start_pos_delta_max = self.config.checks_start_pos_delta_max
|
||||
table.ModelChecks.time_delta_max = self.config.checks_time_delta_max
|
||||
|
||||
|
||||
class MainWindow(QtWidgets.QMainWindow):
|
||||
def __init__(self):
|
||||
def __init__(self, server):
|
||||
super(MainWindow, self).__init__()
|
||||
|
||||
self.server = server
|
||||
self.model = table.CopterDataModel()
|
||||
|
||||
self.ui = Ui_MainWindow()
|
||||
self.ui.setupUi(self)
|
||||
self.init_ui()
|
||||
|
||||
self.model = CopterDataModel()
|
||||
self.proxy_model = CopterProxyModel()
|
||||
self.signals = SignalManager()
|
||||
self.init_model()
|
||||
|
||||
# self.statusBar = QStatusBar()
|
||||
# self.setStatusBar(self.statusBar)
|
||||
# self.statusBar.showMessage("Hey", 2000)
|
||||
|
||||
self.player = QtMultimedia.QMediaPlayer()
|
||||
|
||||
self.init_model()
|
||||
|
||||
self.show()
|
||||
|
||||
def init_ui(self):
|
||||
# Connecting
|
||||
self.ui.check_button.clicked.connect(self.selfcheck_selected)
|
||||
self.ui.start_button.clicked.connect(self.send_start_time_selected)
|
||||
self.ui.pause_button.clicked.connect(self.pause_resume_selected)
|
||||
self.ui.stop_button.clicked.connect(self.land_all)
|
||||
|
||||
self.ui.emergency_button.clicked.connect(self.emergency)
|
||||
self.ui.disarm_button.clicked.connect(partial(self.send_to_selected, "disarm"))
|
||||
self.ui.disarm_all_button.clicked.connect(self.disarm_all)
|
||||
|
||||
self.ui.leds_button.clicked.connect(partial(self.send_to_selected, "led_test"))
|
||||
self.ui.takeoff_button.clicked.connect(self.takeoff_selected)
|
||||
self.ui.flip_button.clicked.connect(self.flip_selected)
|
||||
self.ui.land_button.clicked.connect(partial(self.send_to_selected, "land"))
|
||||
|
||||
self.ui.reboot_fcu.clicked.connect(partial(self.send_to_selected, "reboot_fcu"))
|
||||
self.ui.calibrate_gyro.clicked.connect(self.calibrate_gyro_selected)
|
||||
self.ui.calibrate_level.clicked.connect(self.calibrate_level_selected)
|
||||
|
||||
self.ui.action_remove_row.triggered.connect(self.remove_selected)
|
||||
|
||||
self.ui.action_send_animations.triggered.connect(self.send_animations)
|
||||
self.ui.action_send_calibrations.triggered.connect(self.send_calibrations)
|
||||
self.ui.action_send_configurations.triggered.connect(self.send_configurations)
|
||||
self.ui.action_send_Aruco_map.triggered.connect(self.send_aruco)
|
||||
self.ui.action_send_launch_file.triggered.connect(self.send_launch)
|
||||
self.ui.action_send_fcu_parameters.triggered.connect(self.send_fcu_parameters)
|
||||
self.ui.action_send_any_file.triggered.connect(self.send_any_file)
|
||||
self.ui.action_send_any_command.triggered.connect(self.send_any_command)
|
||||
self.ui.action_restart_clever.triggered.connect(
|
||||
partial(self.send_to_selected, "service_restart", {"name": "clever"}))
|
||||
self.ui.action_restart_clever_show.triggered.connect(
|
||||
partial(self.send_to_selected, "service_restart", {"name": "clever-show"}))
|
||||
self.ui.action_update_client_repo.triggered.connect(partial(self.send_to_selected, "update_repo"))
|
||||
self.ui.action_reboot_all.triggered.connect(partial(self.send_to_selected, "reboot_all"))
|
||||
self.ui.action_set_start_to_current_position.triggered.connect(partial(self.send_to_selected, "move_start"))
|
||||
self.ui.action_reset_start.triggered.connect(partial(self.send_to_selected, "reset_start"))
|
||||
self.ui.action_set_z_offset_to_ground.triggered.connect(partial(self.send_to_selected, "set_z_to_ground"))
|
||||
self.ui.action_reset_z_offset.triggered.connect(partial(self.send_to_selected, "reset_z_offset"))
|
||||
self.ui.action_restart_chrony.triggered.connect(partial(self.send_to_selected, "repair_chrony"))
|
||||
self.ui.action_select_music_file.triggered.connect(self.select_music_file)
|
||||
self.ui.action_play_music.triggered.connect(self.play_music)
|
||||
self.ui.action_stop_music.triggered.connect(self.stop_music)
|
||||
|
||||
self.init_table()
|
||||
|
||||
# Set most safety-important buttons disabled
|
||||
self.ui.start_button.setEnabled(False)
|
||||
self.ui.takeoff_button.setEnabled(False)
|
||||
self.ui.flip_button.setEnabled(False)
|
||||
|
||||
def init_table(self):
|
||||
# remove standard table widget
|
||||
self.ui.horizontalLayout.removeWidget(self.ui.tableView)
|
||||
self.ui.tableView.close()
|
||||
|
||||
# init our custom widget
|
||||
self.ui.copter_table = CopterTableWidget(self.model)
|
||||
self.ui.copter_table.setObjectName("copter_table")
|
||||
|
||||
# add to layout
|
||||
self.ui.horizontalLayout.addWidget(self.ui.copter_table, 0)
|
||||
|
||||
def init_model(self):
|
||||
# self.model.on_id_changed = self.set_copter_id
|
||||
|
||||
self.proxy_model.setDynamicSortFilter(True)
|
||||
self.proxy_model.setSourceModel(self.model)
|
||||
|
||||
# Initiate table and table self.model
|
||||
self.ui.tableView.setModel(self.proxy_model)
|
||||
self.ui.tableView.resizeColumnsToContents()
|
||||
|
||||
self.ui.tableView.doubleClicked.connect(self.selfcheck_info_dialog)
|
||||
|
||||
# Connect signals to manipulate model from threads
|
||||
self.signals.update_data_signal.connect(self.model.update_item)
|
||||
self.signals.add_client_signal.connect(self.model.add_client)
|
||||
self.signals.remove_client_signal.connect(self.model.remove_client)
|
||||
|
||||
# Connect model signals to UI
|
||||
self.model.selected_ready_signal.connect(self.ui.start_button.setEnabled)
|
||||
self.model.selected_takeoff_ready_signal.connect(self.ui.takeoff_button.setEnabled)
|
||||
@@ -109,69 +180,35 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
self.ui.action_select_all_rows.triggered.connect(self.model.select_all)
|
||||
|
||||
def iterate_selected(self, f, *args, **kwargs):
|
||||
for copter in self.model.user_selected():
|
||||
yield f(copter, *args, **kwargs)
|
||||
|
||||
@pyqtSlot()
|
||||
def send_to_selected(self, *args, **kwargs):
|
||||
self.iterate_selected(lambda copter: copter.client.send_message(*args, **kwargs))
|
||||
|
||||
def new_client_connected(self, client: Client):
|
||||
self.signals.add_client_signal.emit(StatedCopterData(copter_id=client.copter_id, client=client))
|
||||
logging.debug("Added client {}".format(client))
|
||||
self.ui.copter_table.add_client(copter_id=client.copter_id, client=client)
|
||||
|
||||
def client_connection_changed(self, client: Client):
|
||||
logging.debug("Start remove {}".format(client.copter_id))
|
||||
logging.debug("Connection {} changed {}".format(client, client.connected), )
|
||||
row_data = self.model.get_row_by_attr("client", client)
|
||||
row_num = self.model.get_row_index(row_data)
|
||||
logging.debug("Removing {}".format(client.copter_id))
|
||||
if row_num is not None:
|
||||
if Server().config.table_remove_disconnected and (not client.connected):
|
||||
client.remove()
|
||||
self.signals.remove_client_signal.emit(row_num)
|
||||
else:
|
||||
self.signals.update_data_signal.emit(row_num, 0, client.connected, ModelStateRole)
|
||||
logging.debug("{} removed".format(client.copter_id))
|
||||
|
||||
def init_ui(self):
|
||||
# Connecting
|
||||
self.ui.check_button.clicked.connect(self.selfcheck_selected)
|
||||
self.ui.start_button.clicked.connect(self.send_starttime_selected)
|
||||
self.ui.pause_button.clicked.connect(self.pause_resume_selected)
|
||||
self.ui.stop_button.clicked.connect(self.land_all)
|
||||
if row_data is None:
|
||||
logging.error("No row for client presented")
|
||||
return
|
||||
|
||||
self.ui.emergency_button.clicked.connect(self.emergency)
|
||||
self.ui.disarm_button.clicked.connect(self.disarm_selected)
|
||||
self.ui.disarm_all_button.clicked.connect(self.disarm_all)
|
||||
|
||||
self.ui.leds_button.clicked.connect(self.test_leds_selected)
|
||||
self.ui.takeoff_button.clicked.connect(self.takeoff_selected)
|
||||
self.ui.flip_button.clicked.connect(self.flip_selected)
|
||||
self.ui.land_button.clicked.connect(self.land_selected)
|
||||
|
||||
self.ui.reboot_fcu.clicked.connect(self.reboot_selected)
|
||||
self.ui.calibrate_gyro.clicked.connect(self.calibrate_gyro_selected)
|
||||
self.ui.calibrate_level.clicked.connect(self.calibrate_level_selected)
|
||||
|
||||
self.ui.action_remove_row.triggered.connect(self.remove_selected)
|
||||
|
||||
self.ui.action_send_animations.triggered.connect(self.send_animations)
|
||||
self.ui.action_send_calibrations.triggered.connect(self.send_calibrations)
|
||||
self.ui.action_send_configurations.triggered.connect(self.send_configurations)
|
||||
self.ui.action_send_Aruco_map.triggered.connect(self.send_aruco)
|
||||
self.ui.action_send_launch_file.triggered.connect(self.send_launch)
|
||||
self.ui.action_send_fcu_parameters.triggered.connect(self.send_fcu_parameters)
|
||||
self.ui.action_send_any_file.triggered.connect(self.send_any_file)
|
||||
self.ui.action_send_any_command.triggered.connect(self.send_any_command)
|
||||
self.ui.action_restart_clever.triggered.connect(self.restart_clever)
|
||||
self.ui.action_restart_clever_show.triggered.connect(self.restart_clever_show)
|
||||
self.ui.action_update_client_repo.triggered.connect(self.update_client_repo)
|
||||
self.ui.action_reboot_all.triggered.connect(self.reboot_all_on_selected)
|
||||
self.ui.action_set_start_to_current_position.triggered.connect(self.update_start_to_current_position)
|
||||
self.ui.action_reset_start.triggered.connect(self.reset_start)
|
||||
self.ui.action_set_z_offset_to_ground.triggered.connect(self.set_z_offset_to_ground)
|
||||
self.ui.action_reset_z_offset.triggered.connect(self.reset_z_offset)
|
||||
self.ui.action_restart_chrony.triggered.connect(self.restart_chrony)
|
||||
self.ui.action_select_music_file.triggered.connect(self.select_music_file)
|
||||
self.ui.action_play_music.triggered.connect(self.play_music)
|
||||
self.ui.action_stop_music.triggered.connect(self.stop_music)
|
||||
|
||||
# Set most safety-important buttons disabled
|
||||
self.ui.start_button.setEnabled(False)
|
||||
self.ui.takeoff_button.setEnabled(False)
|
||||
self.ui.flip_button.setEnabled(False)
|
||||
if Server().config.table_remove_disconnected and (not client.connected):
|
||||
client.remove()
|
||||
self.ui.copter_table.remove_client_data(row_data)
|
||||
logging.debug("Removing from table")
|
||||
else:
|
||||
row_num = self.model.get_row_index(row_data)
|
||||
if row_num is not None:
|
||||
self.ui.copter_table.update_data(row_num, 0, client.connected, table.ModelStateRole)
|
||||
logging.debug("Client status updated")
|
||||
|
||||
@pyqtSlot()
|
||||
def selfcheck_selected(self):
|
||||
@@ -179,149 +216,89 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
client = copter_data_row.client
|
||||
client.get_response("telemetry", self.update_table_data)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def update_table_data(self, message):
|
||||
fields = message.split('`')
|
||||
# copter_id git_version animation_id battery_v battery_p system_status calibration_status mode selfcheck current_position start_position copter_time
|
||||
copter_id = fields[0]
|
||||
git_version = fields[1]
|
||||
animation_id = fields[2]
|
||||
battery_v = fields[3]
|
||||
battery_p = fields[4]
|
||||
if battery_v == 'nan' or battery_p == 'nan':
|
||||
battery_info = "NO_INFO"
|
||||
else:
|
||||
battery_info = "{}V {}%".format(battery_v, battery_p)
|
||||
sys_status = fields[5]
|
||||
cal_status = fields[6]
|
||||
mode = fields[7]
|
||||
selfcheck = fields[8]
|
||||
current_pos = fields[9]
|
||||
start_pos = fields[10]
|
||||
copter_time = fields[11]
|
||||
time_delta = "{}".format(round(float(copter_time) - time.time(), 3))
|
||||
row = self.model.get_row_index(self.model.get_row_by_attr('copter_id', copter_id))
|
||||
self.signals.update_data_signal.emit(row, 1, git_version, ModelDataRole)
|
||||
self.signals.update_data_signal.emit(row, 2, animation_id, ModelDataRole)
|
||||
self.signals.update_data_signal.emit(row, 3, battery_info, ModelDataRole)
|
||||
self.signals.update_data_signal.emit(row, 4, sys_status, ModelDataRole)
|
||||
self.signals.update_data_signal.emit(row, 5, cal_status, ModelDataRole)
|
||||
self.signals.update_data_signal.emit(row, 6, mode, ModelDataRole)
|
||||
self.signals.update_data_signal.emit(row, 7, selfcheck, ModelDataRole)
|
||||
self.signals.update_data_signal.emit(row, 8, current_pos, ModelDataRole)
|
||||
self.signals.update_data_signal.emit(row, 9, start_pos, ModelDataRole)
|
||||
self.signals.update_data_signal.emit(row, 10, time_delta, ModelDataRole)
|
||||
@pyqtSlot(object, dict)
|
||||
def update_table_data(self, client, telems: dict):
|
||||
cols_dict = {
|
||||
"git_version": 1,
|
||||
"animation_id": 2,
|
||||
"battery": 3,
|
||||
"system_status": 4,
|
||||
"calibration_status": 5,
|
||||
"mode": 6,
|
||||
"selfcheck": 7,
|
||||
"current_position": 8,
|
||||
"start_position": 9,
|
||||
"time": 10,
|
||||
}
|
||||
|
||||
@pyqtSlot(QtCore.QModelIndex)
|
||||
def selfcheck_info_dialog(self, index):
|
||||
col = index.column()
|
||||
if col == 6:
|
||||
data = self.proxy_model.data(index, role=ModelDataRole)
|
||||
if data and data != "OK":
|
||||
dialog = QMessageBox()
|
||||
dialog.setIcon(QMessageBox.NoIcon)
|
||||
dialog.setStandardButtons(QMessageBox.Ok)
|
||||
dialog.setWindowTitle("Selfcheck info")
|
||||
dialog.setText("\n".join(data[:10]))
|
||||
dialog.setDetailedText("\n".join(data))
|
||||
dialog.exec()
|
||||
for key, value in telems.items():
|
||||
col = cols_dict.get(key, None)
|
||||
if col is None:
|
||||
logging.error("No column {} present!".format(key))
|
||||
continue
|
||||
|
||||
def _selfcheck_shortener(self, data):
|
||||
shortened = []
|
||||
for line in data:
|
||||
if len(line) > 89:
|
||||
pass
|
||||
return shortened
|
||||
row_data = self.model.get_row_by_attr("client", client)
|
||||
row_num = self.model.get_row_index(row_data)
|
||||
if row_num is not None:
|
||||
self.ui.copter_table.update_data(row_num, col, value, Qt.EditRole)
|
||||
|
||||
@pyqtSlot()
|
||||
def remove_selected(self):
|
||||
for copter in self.model.user_selected():
|
||||
row_num = self.model.get_row_index(copter)
|
||||
if row_num is not None:
|
||||
copter.client.remove()
|
||||
|
||||
if not Server().config.table_remove_disconnected:
|
||||
self.signals.remove_client_signal.emit(row_num)
|
||||
|
||||
logging.info("Client removed from table!")
|
||||
else:
|
||||
logging.error("Client is not in table!")
|
||||
copter.client.remove()
|
||||
if not Server().config.table_remove_disconnected:
|
||||
self.ui.copter_table.remove_client_data(copter)
|
||||
logging.info("Client removed from table!")
|
||||
|
||||
@pyqtSlot()
|
||||
@confirmation_required("This operation will takeoff selected copters with delay and start animation. Proceed?")
|
||||
def send_starttime_selected(self, **kwargs):
|
||||
def send_start_time_selected(self):
|
||||
time_now = server.time_now()
|
||||
dt = self.ui.start_delay_spin.value()
|
||||
logging.info('Wait {} seconds to start animation'.format(dt))
|
||||
if self.ui.music_checkbox.isChecked():
|
||||
music_dt = self.ui.music_delay_spin.value()
|
||||
asyncio.ensure_future(self.play_music_at_time(music_dt+time_now), loop=loop)
|
||||
asyncio.ensure_future(self.play_music_at_time(music_dt + time_now), loop=loop)
|
||||
logging.info('Wait {} seconds to play music'.format(music_dt))
|
||||
# self.selfcheck_selected()
|
||||
for copter in self.model.user_selected():
|
||||
if all_checks(copter):
|
||||
server.send_starttime(copter.client, dt+time_now)
|
||||
for copter in filter(self.model.checks.all_checks, self.model.user_selected()):
|
||||
server.send_starttime(copter.client, dt + time_now)
|
||||
|
||||
@pyqtSlot()
|
||||
def pause_resume_selected(self):
|
||||
if self.ui.pause_button.text() == 'Pause':
|
||||
for copter in self.model.user_selected():
|
||||
copter.client.send_message("pause")
|
||||
self.send_to_selected("pause")
|
||||
self.ui.pause_button.setText('Resume')
|
||||
else:
|
||||
self._resume_selected()
|
||||
|
||||
def _resume_selected(self, **kwargs):
|
||||
time_gap = 0.1
|
||||
for copter in self.model.user_selected():
|
||||
copter.client.send_message('resume', {"time": server.time_now() + time_gap})
|
||||
self.ui.pause_button.setText('Pause')
|
||||
time_gap = 0.1
|
||||
self.send_to_selected("resume", {"time": server.time_now() + time_gap})
|
||||
self.ui.pause_button.setText('Pause')
|
||||
|
||||
@pyqtSlot()
|
||||
def land_all(self):
|
||||
Client.broadcast_message("land")
|
||||
|
||||
@pyqtSlot()
|
||||
def disarm_selected(self):
|
||||
for copter in self.model.user_selected():
|
||||
copter.client.send_message("disarm")
|
||||
|
||||
@pyqtSlot()
|
||||
def test_leds_selected(self):
|
||||
for copter in self.model.user_selected():
|
||||
copter.client.send_message("led_test")
|
||||
|
||||
@pyqtSlot()
|
||||
def disarm_all(self):
|
||||
Client.broadcast_message("disarm")
|
||||
|
||||
@pyqtSlot()
|
||||
@confirmation_required("This operation will takeoff copters immediately. Proceed?")
|
||||
def takeoff_selected(self, **kwargs):
|
||||
def takeoff_selected(self):
|
||||
for copter in self.model.user_selected():
|
||||
if takeoff_checks(copter):
|
||||
if self.model.checks.takeoff_checks(copter):
|
||||
if self.ui.z_checkbox.isChecked():
|
||||
copter.client.send_message("takeoff_z", {"z":str(self.ui.z_spin.value())})
|
||||
copter.client.send_message("takeoff_z", {"z": str(self.ui.z_spin.value())}) # todo int
|
||||
else:
|
||||
copter.client.send_message("takeoff")
|
||||
|
||||
@pyqtSlot()
|
||||
@confirmation_required("This operation will flip(!!!) copters immediately. Proceed?")
|
||||
def flip_selected(self, **kwargs):
|
||||
def flip_selected(self):
|
||||
for copter in self.model.user_selected():
|
||||
if flip_checks(copter):
|
||||
if table.flip_checks(copter):
|
||||
copter.client.send_message("flip")
|
||||
|
||||
@pyqtSlot()
|
||||
def land_selected(self):
|
||||
for copter in self.model.user_selected():
|
||||
copter.client.send_message("land")
|
||||
|
||||
@pyqtSlot()
|
||||
def reboot_selected(self):
|
||||
for copter in self.model.user_selected():
|
||||
copter.client.send_message("reboot_fcu")
|
||||
|
||||
@pyqtSlot()
|
||||
def calibrate_gyro_selected(self):
|
||||
for copter_data_row in self.model.user_selected():
|
||||
@@ -330,9 +307,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
row = self.model.get_row_index(copter_data_row)
|
||||
col = 5
|
||||
data = 'CALIBRATING'
|
||||
self.signals.update_data_signal.emit(row, col, data, ModelDataRole)
|
||||
self.signals.update_data_signal.emit(row, col, data, table.ModelDataRole)
|
||||
# Send request
|
||||
client.get_response("calibrate_gyro", self._get_calibration_info, callback_args=(copter_data_row, ))
|
||||
client.get_response("calibrate_gyro", self._get_calibration_info)
|
||||
|
||||
@pyqtSlot()
|
||||
def calibrate_level_selected(self):
|
||||
@@ -342,15 +319,76 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
row = self.model.get_row_index(copter_data_row)
|
||||
col = 5
|
||||
data = 'CALIBRATING'
|
||||
self.signals.update_data_signal.emit(row, col, data, ModelDataRole)
|
||||
self.signals.update_data_signal.emit(row, col, data, table.ModelDataRole)
|
||||
# Send request
|
||||
client.get_response("calibrate_level", self._get_calibration_info, callback_args=(copter_data_row, ))
|
||||
client.get_response("calibrate_level", self._get_calibration_info)
|
||||
|
||||
def _get_calibration_info(self, value, copter_data_row):
|
||||
def _get_calibration_info(self, client, value):
|
||||
col = 5
|
||||
row = self.model.get_row_index(copter_data_row)
|
||||
data = str(value)
|
||||
self.signals.update_data_signal.emit(row, col, data, ModelDataRole)
|
||||
row_data = self.model.get_row_by_attr("client", client)
|
||||
row = self.model.get_row_index(row_data)
|
||||
if row is not None:
|
||||
data = str(value)
|
||||
self.signals.update_data_signal.emit(row, col, data, table.ModelDataRole)
|
||||
|
||||
def _send_files(self, files, copters=None, client_path="/", client_filename="", match_id=False, callback=None):
|
||||
if copters is None:
|
||||
copters = self.model.user_selected()
|
||||
|
||||
for num, file in enumerate(files):
|
||||
filepath, filename = os.path.split(file)
|
||||
logging.info("Preparing file for sending: {} {}", filepath, filename)
|
||||
|
||||
if match_id:
|
||||
name = os.path.splitext(filename)[0]
|
||||
to_send = filter(lambda copter: bool(re.fullmatch(name, copter.copter_id)), copters) # copter.copter_id == name
|
||||
else:
|
||||
to_send = copters
|
||||
|
||||
to_send = list(to_send)
|
||||
if not to_send:
|
||||
logging.warning("No copters to send file {} to", filename)
|
||||
continue
|
||||
|
||||
filename = client_filename.format(num, filename) or filename
|
||||
|
||||
for copter in to_send:
|
||||
copter.client.send_file(file, os.path.join(client_path, filename))
|
||||
if callback is not None:
|
||||
callback()
|
||||
|
||||
def send_files(self, prompt, ext_filter, copters=None, client_path="/", client_filename="", match_id=False,
|
||||
callback=None):
|
||||
files = QFileDialog.getOpenFileName(self, prompt, filter=ext_filter)
|
||||
if not files:
|
||||
return
|
||||
|
||||
self._send_files(files, copters, client_path, client_filename, match_id, callback)
|
||||
|
||||
def send_directory_files(self, prompt, extensions=(), copters=None, client_path="/", client_filename="",
|
||||
match_id=False, callback=None):
|
||||
path = QFileDialog.getExistingDirectory(self, prompt)
|
||||
|
||||
if not path:
|
||||
return
|
||||
|
||||
if extensions:
|
||||
extensions = ['/*'+ext for ext in extensions]
|
||||
patterns = [os.path.join(path, ext) for ext in extensions]
|
||||
else:
|
||||
patterns = [path+'/*.*']
|
||||
|
||||
files = multi_glob(*patterns)
|
||||
self._send_files(files, copters, client_path, client_filename, match_id, callback)
|
||||
|
||||
@pyqtSlot()
|
||||
def send_any_files(self):
|
||||
copter_path, ok = QInputDialog.getText(self, "Enter path to send on client", "Destination:", QLineEdit.Normal,
|
||||
"/home/pi/")
|
||||
copter_path, ok = QInputDialog.getText(self, "Enter path to send on client", "Destination:", QLineEdit.Normal,
|
||||
"/home/pi/")
|
||||
if not ok:
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def send_animations(self):
|
||||
@@ -379,7 +417,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
for file, name in zip(files, names):
|
||||
for copter in self.model.user_selected():
|
||||
if name == copter.copter_id:
|
||||
copter.client.send_file(file, "/home/pi/catkin_ws/src/clever/clever/camera_info/calibration.yaml")
|
||||
copter.client.send_file(file,
|
||||
"/home/pi/catkin_ws/src/clever/clever/camera_info/calibration.yaml")
|
||||
else:
|
||||
logging.info("Filename has no matches with any drone selected")
|
||||
|
||||
@@ -388,7 +427,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
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 = configparser.ConfigParser() # TODO
|
||||
sendable_config.read(path)
|
||||
options = []
|
||||
for section in sendable_config.sections():
|
||||
@@ -402,7 +441,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
@pyqtSlot()
|
||||
def send_aruco(self):
|
||||
path = QFileDialog.getOpenFileName(self, "Select aruco map configuration file", filter="Aruco map files (*.txt)")[0]
|
||||
path = \
|
||||
QFileDialog.getOpenFileName(self, "Select aruco map configuration file", filter="Aruco map files (*.txt)")[0]
|
||||
if path:
|
||||
filename = os.path.basename(path)
|
||||
print("Selected file:", path, filename)
|
||||
@@ -420,7 +460,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
for file in files:
|
||||
filename = os.path.basename(file)
|
||||
copter.client.send_file(file, "/home/pi/catkin_ws/src/clever/clever/launch/{}".format(filename))
|
||||
|
||||
|
||||
@pyqtSlot()
|
||||
def send_fcu_parameters(self):
|
||||
path = QFileDialog.getOpenFileName(self, "Select px4 param file", filter="px4 params (*.params)")[0]
|
||||
@@ -447,61 +487,17 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
@pyqtSlot()
|
||||
def send_any_command(self):
|
||||
text, okPressed = QInputDialog.getText(self, "Enter command to send on copter","Command:", QLineEdit.Normal, "")
|
||||
text, okPressed = QInputDialog.getText(self, "Enter command to send on copter",
|
||||
"Command:", QLineEdit.Normal, "")
|
||||
if okPressed and text != '':
|
||||
for copter in self.model.user_selected():
|
||||
copter.client.send_message("execute", {"command": text})
|
||||
@pyqtSlot()
|
||||
def restart_clever(self):
|
||||
for copter in self.model.user_selected():
|
||||
copter.client.send_message("service_restart", {"name": "clever"})
|
||||
|
||||
@pyqtSlot()
|
||||
def restart_clever_show(self):
|
||||
for copter in self.model.user_selected():
|
||||
copter.client.send_message("service_restart", {"name": "clever-show"})
|
||||
|
||||
@pyqtSlot()
|
||||
def update_client_repo(self):
|
||||
for copter in self.model.user_selected():
|
||||
copter.client.send_message("update_repo")
|
||||
|
||||
@pyqtSlot()
|
||||
def reboot_all_on_selected(self):
|
||||
for copter in self.model.user_selected():
|
||||
copter.client.send_message("reboot_all")
|
||||
|
||||
@pyqtSlot()
|
||||
def update_start_to_current_position(self):
|
||||
for copter in self.model.user_selected():
|
||||
copter.client.send_message("move_start")
|
||||
|
||||
@pyqtSlot()
|
||||
def reset_start(self):
|
||||
for copter in self.model.user_selected():
|
||||
copter.client.send_message("reset_start")
|
||||
|
||||
@pyqtSlot()
|
||||
def set_z_offset_to_ground(self):
|
||||
for copter in self.model.user_selected():
|
||||
copter.client.send_message("set_z_to_ground")
|
||||
|
||||
@pyqtSlot()
|
||||
def reset_z_offset(self):
|
||||
for copter in self.model.user_selected():
|
||||
copter.client.send_message("reset_z_offset")
|
||||
|
||||
@pyqtSlot()
|
||||
def restart_chrony(self):
|
||||
for copter in self.model.user_selected():
|
||||
copter.client.send_message("repair_chrony")
|
||||
self.send_to_selected("execute", {"command": text})
|
||||
|
||||
@pyqtSlot()
|
||||
def select_music_file(self):
|
||||
path = QFileDialog.getOpenFileName(self, "Select music file", filter="Music files (*.mp3 *.wav)")[0]
|
||||
if path:
|
||||
media = QUrl.fromLocalFile(path)
|
||||
content = QtMultimedia.QMediaContent(media)
|
||||
content = QtMultimedia.QMediaContent(media)
|
||||
self.player.setMedia(content)
|
||||
self.ui.action_select_music_file.setText(self.ui.action_select_music_file.text() + " (selected)")
|
||||
|
||||
@@ -513,9 +509,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
if self.player.mediaStatus() == QtMultimedia.QMediaPlayer.NoMedia:
|
||||
logging.info("No media file")
|
||||
return
|
||||
|
||||
|
||||
if self.player.state() == QtMultimedia.QMediaPlayer.StoppedState or \
|
||||
self.player.state() == QtMultimedia.QMediaPlayer.PausedState:
|
||||
self.player.state() == QtMultimedia.QMediaPlayer.PausedState:
|
||||
self.ui.action_play_music.setText("Pause music")
|
||||
self.player.play()
|
||||
else:
|
||||
@@ -546,22 +542,22 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.player.play()
|
||||
|
||||
@pyqtSlot()
|
||||
def emergency(self):
|
||||
def emergency(self): # TODO refactor for the sake of god
|
||||
client_row_min = 0
|
||||
client_row_max = self.model.rowCount() - 1
|
||||
result = -1
|
||||
while (result != 0) and (result != 3) and (result != 4):
|
||||
# light_green_red(min, max)
|
||||
client_row_mid = int(math.ceil((client_row_max+client_row_min) / 2.0))
|
||||
client_row_mid = int(math.ceil((client_row_max + client_row_min) / 2.0))
|
||||
print(client_row_min, client_row_mid, client_row_max)
|
||||
for row_num in range(client_row_min, client_row_mid):
|
||||
self.model.data_contents[row_num].client\
|
||||
self.model.data_contents[row_num].client \
|
||||
.send_message("led_fill", {"green": 255})
|
||||
for row_num in range(client_row_mid, client_row_max + 1):
|
||||
self.model.data_contents[row_num].client \
|
||||
.send_message("led_fill", {"red": 255})
|
||||
|
||||
Dialog = QtWidgets.QDialog()
|
||||
Dialog = QtWidgets.QDialog()
|
||||
ui = Ui_Dialog()
|
||||
ui.setupUi(Dialog)
|
||||
Dialog.show()
|
||||
@@ -574,7 +570,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.model.data_contents[row_num].client \
|
||||
.send_message("led_fill")
|
||||
client_row_max = client_row_mid - 1
|
||||
|
||||
|
||||
elif result == 2:
|
||||
for row_num in range(client_row_min, client_row_mid):
|
||||
self.model.data_contents[row_num].client \
|
||||
@@ -592,10 +588,15 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.model.data_contents[row_num].client \
|
||||
.send_message("disarm")
|
||||
|
||||
@messaging.message_callback("telem")
|
||||
def get_telem_data(*args, **kwargs):
|
||||
message = kwargs.get("message", None)
|
||||
window.update_table_data(message)
|
||||
|
||||
@messaging.message_callback("telemetry")
|
||||
def get_telem_data(self, **kwargs):
|
||||
message = kwargs.get("value")
|
||||
window.update_table_data(self, message)
|
||||
|
||||
|
||||
def except_hook(cls, exception, traceback):
|
||||
sys.__excepthook__(cls, exception, traceback)
|
||||
|
||||
|
||||
def set_taskbar_icon():
|
||||
@@ -606,7 +607,33 @@ def set_taskbar_icon():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
msgbox_handler = ExitMsgbox()
|
||||
msgbox_handler.setLevel(logging.CRITICAL)
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format="%(asctime)s [%(name)-7.7s] [%(threadName)-19.19s] [%(levelname)-7.7s] %(message)s",
|
||||
handlers=[
|
||||
logging.FileHandler("server_logs/{}.log".format(now)),
|
||||
logging.StreamHandler(),
|
||||
msgbox_handler
|
||||
])
|
||||
|
||||
sys.excepthook = except_hook # for debugging (exceptions traceback)
|
||||
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
splash_pix = QPixmap('icons/coex_splash.jpg')
|
||||
|
||||
splash = QSplashScreen(splash_pix)
|
||||
splash.setEnabled(False)
|
||||
|
||||
splash.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
|
||||
progressBar = QProgressBar(splash)
|
||||
progressBar.setGeometry(25, splash_pix.height() - 80, splash_pix.width(), 35)
|
||||
splash.showMessage("Loading clever-show server"+"\n\n\n\n\n", int(Qt.AlignBottom | Qt.AlignCenter), Qt.white)
|
||||
app.processEvents()
|
||||
splash.show()
|
||||
# time.sleep(3)
|
||||
|
||||
app_icon = QtGui.QIcon()
|
||||
app_icon.addFile('icons/image.ico', QtCore.QSize(256, 256))
|
||||
@@ -618,16 +645,21 @@ if __name__ == "__main__":
|
||||
loop = QEventLoop(app)
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
#app.exec_()
|
||||
# app.exec_()
|
||||
with loop:
|
||||
window = MainWindow()
|
||||
server = ServerQt()
|
||||
window = MainWindow(server)
|
||||
|
||||
Client.on_first_connect = window.new_client_connected
|
||||
Client.on_connect = window.client_connection_changed
|
||||
Client.on_disconnect = window.client_connection_changed
|
||||
|
||||
server = Server(on_stop=app.quit)
|
||||
server.start()
|
||||
|
||||
window.show()
|
||||
window.send_directory_files("lol")
|
||||
splash.close()
|
||||
|
||||
loop.run_forever()
|
||||
|
||||
server.stop()
|
||||
|
||||
Reference in New Issue
Block a user