Rename Server -> server

This commit is contained in:
Arthur Golubtsov
2020-06-02 17:32:33 +03:00
parent 6ee48640ed
commit 9dfb4af621
15 changed files with 0 additions and 0 deletions

7
server/README.md Normal file
View File

@@ -0,0 +1,7 @@
# Server of clever-show
Application for creating and running drone shows, adjusting drones, animations and music.
Documentation is located here:
* English
* [Russian](../docs/ru/server.md)

11
server/chrony.conf Normal file
View File

@@ -0,0 +1,11 @@
pool 0.ru.pool.ntp.org iburst minpoll 10
pool 1.ru.pool.ntp.org iburst minpoll 10
pool 2.ru.pool.ntp.org iburst minpoll 10
pool 3.ru.pool.ntp.org iburst minpoll 10
driftfile /var/lib/chrony/drift
local stratum 8
allow 192.168.0.0/16
makestep 1.0 3
smoothtime 50000 0.01
rtcsync

View File

@@ -0,0 +1,55 @@
config_name = string(default='server')
config_version = float(default='1.0')
[SERVER]
port = integer(default=25000)
buffer_size = integer(default=1024)
[CLIENT]
clever_dir = string(default=/home/pi/catkin_ws/src/clever/clover)
[TABLE]
# True -> clients are removed on disconnection
# False -> disconnected clients indicated
remove_disconnected = boolean(default=False)
[[PRESETS]]
current = string(default="DEFAULT")
[[[DEFAULT]]]
copter_id = preset_param(default=list(True, 100))
git_version = preset_param(default=list(True, 75))
config_version = preset_param(default=list(True, 105))
animation_info = preset_param(default=list(True, 100))
battery = preset_param(default=list(True, 100))
fcu_status = preset_param(default=list(True, 100))
calibration_status = preset_param(default=list(True, 65))
mode = preset_param(default=list(True, 100))
selfcheck = preset_param(default=list(True, 65))
current_position = preset_param(default=list(True, 250))
start_position = preset_param(default=list(True, 240))
last_task = preset_param(default=list(True, 275))
time_delta = preset_param(default=list(True, 70))
[[[__many__]]]
__many__ = preset_param
[CHECKS]
check_git_version = boolean(default=True)
check_current_position = boolean(default=True)
# in percents; set 0 to disable this check
battery_min = float(default=50.0, min=0, max=100)
# in meters; set 0 to disable this check
start_pos_delta_max = float(default=1.0, min=0)
# in seconds
time_delta_max = float(default=1.0, min=0)
[BROADCAST]
send = boolean(default=True)
listen = boolean(default=True)
port = integer(default=8181)
send_ip = string(default=255.255.255.255)
# delay for message sending in seconds
delay = float(default=5.0, min=0)
[NTP]
use = boolean(default=False)
host = string(default=ntp1.stratum2.ru)
port = integer(default=123)

72
server/config_editor.py Normal file
View File

@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'config_editor.ui'
#
# Created by: PyQt5 UI code generator 5.14.0
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_config_dialog(object):
def setupUi(self, config_dialog):
config_dialog.setObjectName("config_dialog")
config_dialog.resize(600, 700)
config_dialog.setModal(False)
self.gridLayout = QtWidgets.QGridLayout(config_dialog)
self.gridLayout.setObjectName("gridLayout")
self.config_view = QtWidgets.QTreeView(config_dialog)
self.config_view.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked|QtWidgets.QAbstractItemView.EditKeyPressed|QtWidgets.QAbstractItemView.SelectedClicked)
self.config_view.setObjectName("config_view")
self.config_view.header().setCascadingSectionResizes(False)
self.config_view.header().setDefaultSectionSize(250)
self.gridLayout.addWidget(self.config_view, 0, 0, 1, 1)
self.gridLayout_2 = QtWidgets.QGridLayout()
self.gridLayout_2.setObjectName("gridLayout_2")
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.gridLayout_2.addItem(spacerItem, 0, 2, 1, 1)
self.do_restart = QtWidgets.QCheckBox(config_dialog)
self.do_restart.setObjectName("do_restart")
self.gridLayout_2.addWidget(self.do_restart, 0, 1, 1, 1)
self.buttonBox = QtWidgets.QDialogButtonBox(config_dialog)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Save)
self.buttonBox.setObjectName("buttonBox")
self.gridLayout_2.addWidget(self.buttonBox, 0, 4, 1, 1)
self.do_coloring = QtWidgets.QCheckBox(config_dialog)
self.do_coloring.setChecked(True)
self.do_coloring.setObjectName("do_coloring")
self.gridLayout_2.addWidget(self.do_coloring, 0, 0, 1, 1)
self.save_as_button = QtWidgets.QPushButton(config_dialog)
self.save_as_button.setObjectName("save_as_button")
self.gridLayout_2.addWidget(self.save_as_button, 0, 3, 1, 1)
self.gridLayout.addLayout(self.gridLayout_2, 2, 0, 1, 1)
self.line = QtWidgets.QFrame(config_dialog)
self.line.setFrameShape(QtWidgets.QFrame.HLine)
self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
self.line.setObjectName("line")
self.gridLayout.addWidget(self.line, 1, 0, 1, 1)
self.retranslateUi(config_dialog)
self.buttonBox.accepted.connect(config_dialog.accept)
self.buttonBox.rejected.connect(config_dialog.reject)
QtCore.QMetaObject.connectSlotsByName(config_dialog)
def retranslateUi(self, config_dialog):
_translate = QtCore.QCoreApplication.translate
config_dialog.setWindowTitle(_translate("config_dialog", "Config Editor"))
self.do_restart.setText(_translate("config_dialog", "Restart"))
self.do_restart.setShortcut(_translate("config_dialog", "R"))
self.do_coloring.setText(_translate("config_dialog", "Color Indication"))
self.save_as_button.setText(_translate("config_dialog", "Save as"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
config_dialog = QtWidgets.QDialog()
ui = Ui_config_dialog()
ui.setupUi(config_dialog)
config_dialog.show()
sys.exit(app.exec_())

128
server/config_editor.ui Normal file
View File

@@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>config_dialog</class>
<widget class="QDialog" name="config_dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>700</height>
</rect>
</property>
<property name="windowTitle">
<string>Config Editor</string>
</property>
<property name="modal">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QTreeView" name="config_view">
<property name="editTriggers">
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
</property>
<attribute name="headerCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="headerDefaultSectionSize">
<number>250</number>
</attribute>
</widget>
</item>
<item row="2" column="0">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="2">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="do_restart">
<property name="text">
<string>Restart</string>
</property>
<property name="shortcut">
<string>R</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="do_coloring">
<property name="text">
<string>Color Indication</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QPushButton" name="save_as_button">
<property name="text">
<string>Save as</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>config_dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>260</x>
<y>237</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>246</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>config_dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>260</x>
<y>239</y>
</hint>
<hint type="destinationlabel">
<x>267</x>
<y>246</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,954 @@
import pickle
import logging
from ast import literal_eval
from functools import partial
from copy import deepcopy
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtGui import QCursor, QKeySequence
from PyQt5.QtWidgets import QAbstractItemView, QTreeView, QMenu, QAction, QMessageBox, QInputDialog, QFileDialog, \
QShortcut
import config_editor
import sys
import os, 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)
import config
states_colors = {
'normal': Qt.white,
'unchanged': Qt.blue,
'default': Qt.cyan,
'edited': Qt.yellow,
'added': Qt.green,
'deleted': Qt.red,
}
StateRole = 999
TypeRole = 998
def convert_type(data):
try:
data = literal_eval(data) if data else None
except (SyntaxError, ValueError):
data = str(data)
return data
class ConfigModelItem:
def __init__(self, values=(None, None, None, None), item_type='option',
state='normal', default=None, parent=None):
self.spec_default = default
self.itemData = list(values)
self.state = state
self.type = item_type
if isinstance(self.data(1), (list, tuple)):
self.type = 'list'
self.default_values = deepcopy(self.itemData)
self.default_state = state
self.childItems = []
self.parentItem = parent
self.setup_type()
if self.parentItem is not None:
self.parentItem.appendChild(self)
def setup_type(self):
if self.type == 'section':
self.itemData[1:1] = ('<section>',)
self.spec_default = self.data(1)
elif self.type == 'list':
self._setup_list(self.get_list_items())
def _get_list_spec(self):
data = self.data(1)
comments = self.data(2)
if comments:
try:
raw_spec = comments.split('\n')[-1].split()[1:]
if raw_spec[0] == '__list__': # and len(raw_spec[1:]) == len(data):
return raw_spec[1:]
except IndexError:
pass
return list(map(str, range(len(data))))
def get_list_items(self):
spec = self._get_list_spec()
values = self.data(1)
if isinstance(self.spec_default, list):
defaults = self.spec_default
else:
defaults = (None, )*len(spec)
self.itemData[1] = '<list: {}>'.format(' '.join(spec))
# self.spec_default = self.itemData[1]
for key, value, default in zip(spec, values, defaults):
yield ConfigModelItem((key, value, None, None), item_type='list_item',
state=self.state, default=default)
def _setup_list(self, items): # use only at initialization
for child in items:
self.appendChild(child)
@property
def is_section(self): # probably deprecated
return self.type == 'section'
def appendChild(self, item):
self.childItems.append(item)
item.parentItem = self
def addChildren(self, items, row):
if row == -1:
row = 0
self.childItems[row:row] = items
for item in items:
item.parentItem = self
def child(self, row):
return self.childItems[row]
def childCount(self):
return len(self.childItems)
def columnCount(self):
return len(self.itemData)
def data(self, column):
try:
return self.itemData[column]
except IndexError:
return None
def set_data(self, data, column):
old_data = self.data(column)
if old_data is None:
data = convert_type(data)
if data == '<list>':
data = []
try:
self.itemData[column] = data
except IndexError:
return False
if old_data != data:
self.set_state('edited')
self.check_state()
return True
def check_state(self):
if self.spec_default is not None and self.data(1) == self.spec_default \
and self.data(0) == self.default_values[0] and self.type != 'section':
self.set_state('default')
# print('def', self.data(1), self.data(0), self.spec_default)
child_states = [child.state for child in self.childItems]
if any(state in child_states for state in ['edited', 'added', 'deleted']):
self.state = 'edited'
if len(set(child_states)) == 1: # if all states equal
self.set_state(child_states[0], set_children=False)
# print(child_states)
if self.parentItem is not None:
self.parentItem.check_state()
def set_state(self, state, set_children=True):
if self.state == 'unchanged' and state == 'default':
return
if self.state == 'added' and state in ('edited', 'unchanged', 'default', 'normal'):
return
self.state = state
if set_children: # to prevent cycle state set
for child in self.childItems:
child.set_state(state)
# if state == 'edited':
# self.parentItem.state = state
def set_type(self, item_type):
self.type = item_type
def parent(self):
return self.parentItem
def row(self):
if self.parentItem is not None:
return self.parentItem.childItems.index(self)
return 0
def removeChild(self, position):
if position < 0 or position > len(self.childItems):
return False
child = self.childItems.pop(position)
child.parentItem = None
return True
def __repr__(self):
return str(self.itemData)
def ensure_unique_names(item, include_self=True):
name = item.data(0)
siblings_names = [child.data(0) for child in item.parent().childItems]
if not include_self:
siblings_names.remove(name)
while name in siblings_names:
if '_copy' in name:
spl = name.split('_copy')
num = int(spl[1]) if spl[1] else 0
num += 1
name = spl[0] + '_copy' + str(num)
else:
name = name + '_copy'
item.set_data(name, 0)
class ConfigModel(QtCore.QAbstractItemModel):
def __init__(self, parent=None, widget=None,
headers=("Option", "Value", 'Comment', 'Inline Comment')):
self.rootItem = ConfigModelItem(headers)
super(ConfigModel, self).__init__(parent)
self.widget = widget
self.do_color = True
self.initial_comment = ''
self.final_comment = ''
@QtCore.pyqtSlot(int)
def enable_color(self, value):
self.do_color = value
self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex(), (Qt.BackgroundRole, ))
def headerData(self, section, orientation, role):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
return self.rootItem.data(section)
def columnCount(self, parent):
return self.rootItem.columnCount()
def rowCount(self, parent):
if parent.column() > 0:
return 0
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
return parentItem.childCount()
def childrenIndexes(self, parent):
column = parent.column()
parent = self.index(parent.row(), 0, parent.parent())
for i in range(self.rowCount(parent)):
yield self.index(i, column, parent)
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
parentItem = self.nodeFromIndex(parent)
childItem = parentItem.child(row)
if childItem:
return self.createIndex(row, column, childItem)
else:
return QtCore.QModelIndex()
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
childItem = index.internalPointer()
if not isinstance(childItem, ConfigModelItem):
# print(childItem, index.column()), # index.row(), index.parent().internalPointer())
return QtCore.QModelIndex()
parentItem = childItem.parent()
if parentItem == self.rootItem: #or parentItem is None:
return QtCore.QModelIndex()
return self.createIndex(parentItem.row(), 0, parentItem)
def modifyCol(self, index, col):
return self.index(index.row(), col, index.parent())
def nodeFromIndex(self, index):
if index.isValid():
return index.internalPointer()
return self.rootItem
def data(self, index, role):
if not index.isValid():
return None
item = index.internalPointer()
if role == Qt.DisplayRole or role == Qt.EditRole:
return item.data(index.column())
if role == Qt.BackgroundRole and self.do_color:
return QtGui.QBrush(states_colors[item.state])
if role == StateRole:
return item.state
if role == TypeRole:
return item.type
return None
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid():
return False
item = index.internalPointer()
if role == Qt.EditRole:
column = index.column()
if column == 0 and value != item.data(column):
if not self.widget.edit_caution():
return False
item.set_data(value, column)
if column == 0:
ensure_unique_names(item, include_self=False)
elif column == 1 and isinstance(item.data(1), (list, tuple)) \
and item.type not in ('list', 'list_item'):
item.set_type('list')
self.insertItems(0, list(item.get_list_items()), index)
self.widget.ui.config_view.expandAll()
elif role == StateRole:
item.set_state(value)
elif role == TypeRole:
# if value != item.type and value == 'list': # when list is created:
# pass
item.set_type(value)
self.dataChanged.emit(index, index, (role,))
return True
def flags(self, index):
if not index.isValid():
return QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled # Qt.NoItemFlags
item = index.internalPointer()
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
if index.column() == 0:
if item.type != 'list_item':
flags |= int(Qt.ItemIsDragEnabled)
if item.type == 'section':
flags |= int(Qt.ItemIsDropEnabled)
not_section = not (index.column() > 0 and item.type == 'section')
not_list_item = not (index.column() > 1 and item.type == 'list_item')
not_list_val = not (index.column() == 1 and item.type == 'list')
if not_section and not_list_item and not_list_val:
flags |= Qt.ItemIsEditable
return flags
def supportedDropActions(self):
return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction
def mimeTypes(self):
return ['app/configitem']
def mimeData(self, indexes):
mimedata = QtCore.QMimeData()
index = indexes[0]
mimedata.setData('app/configitem', pickle.dumps(self.nodeFromIndex(index)))
return mimedata
def dropMimeData(self, mimedata, action, row, column, parentIndex):
if action == Qt.IgnoreAction:
return True
droppedNode = deepcopy(pickle.loads(mimedata.data('app/configitem')))
self.insertItems(row, [droppedNode], parentIndex)
self.dataChanged.emit(parentIndex, parentIndex)
self.widget.ui.config_view.expandAll()
if action & Qt.CopyAction:
return False # to not delete original item
return True
def removeRows(self, row, count, parent):
self.beginRemoveRows(parent, row, row + count - 1)
parentItem = self.nodeFromIndex(parent)
for _ in range(count):
parentItem.removeChild(row)
self.endRemoveRows()
return True
def removeRow(self, index):
parent = index.parent()
self.beginRemoveRows(parent, index.row(), index.row())
parentItem = self.nodeFromIndex(parent)
parentItem.removeChild(index.row())
self.endRemoveRows()
return True
def insertItems(self, row, items, parentIndex):
parent = self.nodeFromIndex(parentIndex)
self.beginInsertRows(parentIndex, row, row + len(items) - 1) # parentIndex or QtCore.QModelIndex()
parent.addChildren(items, row)
self.endInsertRows()
self.update_all()
return True
def update_all(self):
self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())
def dict_setup(self, data: dict, parent=None, convert_types=False):
if parent is None:
parent = self.rootItem
for key, value in data.items():
if isinstance(value, dict):
item = ConfigModelItem((key,), parent=parent, item_type='section')
self.dict_setup(value, parent=item)
else:
if convert_types:
value = convert_type(value)
parent.appendChild(ConfigModelItem((key, value, '', '')))
def config_dict_setup(self, data: dict, convert_types=False, parent=None):
if parent is None:
parent = self.rootItem
self.initial_comment = '\n'.join(data.pop('initial_comment', ['']))
self.final_comment = '\n'.join(data.pop('final_comment', ['']))
for key, item in data.items():
if '__value__' in item:
value = item.get('__value__')
if convert_types:
value = convert_type(value)
default = item['default']
comments = '\n'.join(item.get('comments', '')) or ''
inline_comment = item.get('inline_comment', '') or ''
if item['unchanged']:
state = 'unchanged'
elif value == default:
state = 'default'
else:
state = 'normal'
parent.appendChild(ConfigModelItem((key, value, comments, inline_comment),
state=state, default=default))
else:
section = ConfigModelItem((key,), parent=parent, item_type='section')
self.config_dict_setup(item, convert_types=convert_types, parent=section)
section.check_state()
def to_dict(self, parent=None) -> dict:
if parent is None:
parent = self.rootItem
data = {}
for item in parent.childItems:
item_name, item_data = item.data(0), item.data(1)
if item.is_section:
data[item_name] = self.to_dict(item)
else:
data[item_name] = item_data
return data
def to_config_dict(self, parent=None) -> dict:
data = {}
if parent is None:
parent = self.rootItem
data['initial_comment'] = self.initial_comment.split('\n')
data['final_comment'] = self.final_comment.split('\n')
for item in parent.childItems:
key = item.data(0)
if item.is_section:
d = self.to_config_dict(item)
if d: # to prevent empty sections
data[key] = d
elif item.state not in ('unchanged', 'deleted'):
if item.type == 'list':
value = [child.data(1) for child in item.childItems]
else:
value = item.data(1)
d = {'__value__': value}
comment = item.data(2)
if comment:
d.update({'comments': comment.split('\n')})
inline_comment = item.data(3)
if inline_comment:
d.update({'inline_comment': inline_comment})
data[key] = d
return data
@property
def dict(self):
return self.to_dict()
class ConfigTreeWidget(QTreeView):
def __init__(self):
QTreeView.__init__(self)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.open_menu)
self.setSelectionMode(self.SingleSelection)
# self.setSelectionBehavior(self.SelectItems)
self.setDragDropMode(QAbstractItemView.DragDrop)
self.setDefaultDropAction(Qt.MoveAction)
self.setDragEnabled(True)
self.setAcceptDrops(True)
self.setDropIndicatorShown(True)
self.setAnimated(True)
self.duplicate_shortcut = QShortcut(QKeySequence('Shift+D'), self)
self.duplicate_shortcut.activated.connect(self.with_selected(self.duplicate))
self.exclude_shortcut = QShortcut(QKeySequence('Alt+Del'), self)
self.exclude_shortcut.activated.connect(self.with_selected(self.exclude))
self.remove_shortcut = QShortcut(QKeySequence('Del'), self)
self.remove_shortcut.activated.connect(self.with_selected(self.remove))
self.clear_shortcut = QShortcut(QKeySequence('Shift+R'), self)
self.clear_shortcut.activated.connect(self.with_selected(self.reset_item, 'clear_value'))
self.default_shortcut = QShortcut(QKeySequence('Ctrl+R'), self)
self.default_shortcut.activated.connect(self.with_selected(self.reset_item, 'default'))
self.reset_shortcut = QShortcut(QKeySequence('Alt+R'), self)
self.reset_shortcut.activated.connect(self.with_selected(self.reset_item, 'all'))
self.item_shortcut = QShortcut(QKeySequence('Shift+A'), self)
self.item_shortcut.activated.connect(self.with_selected(self.add_item, False))
self.section_shortcut = QShortcut(QKeySequence('Ctrl+A'), self)
self.section_shortcut.activated.connect(self.with_selected(self.add_item, True))
def with_selected(self, f, *args, **kwargs):
def decorated():
index = self.selectedIndexes()[0]
return f(index, *args, **kwargs)
return decorated
def open_menu(self, point):
index = self.indexAt(point)
item = index.internalPointer()
menu = QMenu()
duplicate = QAction("Duplicate")
duplicate.setShortcut(self.duplicate_shortcut.key())
duplicate.triggered.connect(partial(self.duplicate, index))
menu.addAction(duplicate)
exclude = QAction("Toggle exclude")
exclude.setShortcut(self.exclude_shortcut.key())
exclude.triggered.connect(partial(self.exclude, index))
menu.addAction(exclude)
remove = QAction("Remove from config")
remove.setShortcut(self.remove_shortcut.key())
remove.triggered.connect(partial(self.remove, index))
menu.addAction(remove)
menu.addSeparator()
clear = QAction("Clear item value")
clear.setShortcut(self.clear_shortcut.key())
clear.triggered.connect(partial(self.reset_item, index, 'clear_value'))
menu.addAction(clear)
reset_default = QAction("Reset value to default")
reset_default.setShortcut(self.default_shortcut.key())
reset_default.triggered.connect(partial(self.reset_item, index, 'default'))
menu.addAction(reset_default)
reset_all = QAction("Reset all changes")
reset_all.setShortcut(self.reset_shortcut.key())
reset_all.triggered.connect(partial(self.reset_item, index, 'all'))
menu.addAction(reset_all)
menu.addSeparator()
add_option = QAction("Add option")
add_option.setShortcut(self.item_shortcut.key())
add_option.triggered.connect(partial(self.add_item, index, False))
menu.addAction(add_option)
add_section = QAction("Add section")
add_section.setShortcut(self.section_shortcut.key())
add_section.triggered.connect(partial(self.add_item, index, True))
menu.addAction(add_section)
if item is None:
clear.setDisabled(True)
reset_all.setDisabled(True)
reset_default.setDisabled(True)
duplicate.setDisabled(True)
remove.setDisabled(True)
exclude.setDisabled(True)
else:
if item.type in ('list', 'list_item'):
add_section.setDisabled(True)
if item.type == 'list':
clear.setDisabled(True) # Temporary, cuz buggg
# if item.type == 'section':
# clear.setDisabled(True)
menu.exec_(QCursor.pos())
def duplicate(self, index):
item = deepcopy(index.internalPointer())
item.set_state('added')
ensure_unique_names(item)
self.model().insertItems(index.row() + 1, [item], index.parent())
self.expandAll() # fixes not expanded duplicated section
def remove(self, index):
self.model().removeRow(index)
def exclude(self, index):
item = self.model().nodeFromIndex(index)
if item.state == 'deleted':
self.model().setData(index, item.previous_state, StateRole)
else:
self.model().setData(index, 'deleted', StateRole)
def add_item(self, index, is_section):
parentItem = self.model().nodeFromIndex(index)
if parentItem.type in ('list', 'list_item'):
if is_section:
return
item_type = 'list_item'
else:
item_type = 'section' if is_section else 'option'
prompt = 'Enter {} name'.format(item_type.replace('_', ' '))
text, ok = QInputDialog.getText(self, prompt, prompt)
if not ok:
return
if parentItem.type in ('list', 'section'): # to append at first index in section or list
row = 0
parent = index
else:
row = index.row()
parent = index.parent()
if row == -1: # to append at last position e.g. at root
row = parentItem.childCount()
else:
row += 1 # to append under current position
item = ConfigModelItem((text, None, '', ''), item_type=item_type, state='added')
self.model().insertItems(row, [item], parent)
ensure_unique_names(item, include_self=False)
# parent.internalPointer().set_state('edited')
self.expandAll()
def reset_item(self, index, reset_type):
item = index.internalPointer()
model = self.model()
itemdataindex = model.modifyCol(index, 1)
if reset_type == 'all':
for i, default in enumerate(item.default_values):
model.setData(model.modifyCol(index, i), default)
model.setData(index, item.default_state, role=StateRole)
elif reset_type == 'default':
# if item.type == 'list' and \
# not isinstance(item.spec_default, (list, tuple)):
# self.reset_item(item, 'clear_value')
model.setData(itemdataindex, item.spec_default)
if item.default_state == 'unchanged':
model.setData(index, 'unchanged', role=StateRole)
elif reset_type == 'clear_value':
item_type = model.data(itemdataindex, TypeRole)
if item_type == 'list':
return
if item_type != 'section':
model.setData(itemdataindex, None)
# if model.data(itemdataindex, TypeRole) == 'list': # TODO
# model.removeRows(0, item.childCount(), index)
# model.setData(index, 'option', role=TypeRole)
# return
for child in model.childrenIndexes(index):
self.reset_item(child, reset_type)
class ConfigDialog(QtWidgets.QDialog):
copter_editor_signal = QtCore.pyqtSignal(object, object)
def __init__(self, parent=None):
super(ConfigDialog, self).__init__(parent)
self.ui = config_editor.Ui_config_dialog()
self.model = ConfigModel(widget=self)
self._filename = None
self.unsaved = False
self.setupUi()
self.copter_editor_signal.connect(self._call_copter_dialog)
@property
def filename(self):
return self._filename or 'Untitled.ini'
def setupModel(self, data, pure_dict=False, convert_types=False):
if pure_dict:
self.model.dict_setup(data, convert_types=convert_types)
else:
self.model.config_dict_setup(data, convert_types=convert_types)
self.ui.config_view.expandAll()
self.ui.config_view.resizeColumnToContents(0)
self.ui.config_view.resizeColumnToContents(1)
self.model.dataChanged.connect(self.unsaved_call) # connect after setup
def setupUi(self):
self.ui.setupUi(self)
self.ui.config_view = ConfigTreeWidget()
self.ui.config_view.setObjectName("config_view")
self.ui.config_view.setModel(self.model)
self.ui.gridLayout.addWidget(self.ui.config_view, 0, 0, 1, 1)
self.ui.config_view.expandAll()
self.ui.do_coloring.stateChanged.connect(self.model.enable_color)
self.ui.save_as_button.clicked.connect(self.save_as)
# self.ui.delete_button.pressed.connect(self.remove_selected)
def update_title(self):
self.setWindowTitle(f"Config editor - {self.filename}" + "*"*self.unsaved)
def unsaved_call(self):
self.unsaved = True
self.update_title()
self.model.dataChanged.disconnect(self.unsaved_call)
def closeEvent(self, event):
if not self.unsaved or self.result():
event.accept()
return
reply = QMessageBox.question(self, "Confirm exit", "There are unsaved changes in config file. "
"Are you sure you want to exit?",
QMessageBox.No | QMessageBox.Yes, QMessageBox.No)
if reply != QMessageBox.Yes:
event.ignore()
else:
event.accept()
def edit_caution(self):
reply = QMessageBox().warning(self, "Editing caution",
"Are you sure you want to edit section/option name? "
"Proceed with caution!",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No
)
return reply == QMessageBox.Yes
def save_as(self):
save_path = QFileDialog.getSaveFileName(self, "Save as configuration file (.ini)",
directory=self.filename+'.ini',
options=QFileDialog.DontConfirmOverwrite,
filter="Config files (*.ini);;All files (*.*)")[0]
if not save_path:
return
split_path = save_path.split('.')
if not (len(split_path) > 1 and split_path[-1] == 'ini'):
save_path += '.ini'
cfg = config.ConfigManager()
cfg.load_from_dict(self.model.to_config_dict())
cfg.config.filename = save_path
cfg.write()
@pyqtSlot()
def run(self):
self.show()
self.exec()
return self.result()
def validation_loop(self, cfg, configspec=None): # modifies cfg object
filename = cfg.config.filename
while True:
if not self.run():
return False
try:
cfg.load_from_dict(self.model.to_config_dict(), configspec=configspec)
except config.ValidationError as error:
msg = "Can not validate. Proceed with editing? Errors: \n" + "\n".join(error.flatten_errors())
reply = QMessageBox.warning(self, "Validation error!", msg, QMessageBox.Yes | QMessageBox.Cancel)
if reply == QMessageBox.Cancel:
return False
else:
return True
finally:
if filename is not None:
cfg.config.filename = filename
def call_copter_dialog(self, client, value):
self.copter_editor_signal.emit(client, value)
@pyqtSlot(object, object)
def _call_copter_dialog(self, client, value):
logging.info("Opening copter config dialog")
config_dict, spec_dict = value["config"], value["configspec"]
cfg = config.ConfigManager()
cfg.load_from_dict(config_dict, spec_dict)
def save_callback():
edited_dict = cfg.full_dict(include_defaults=False)
client.send_message("config", kwargs={"config": edited_dict, "mode": "rewrite"})
def restart_callback():
client.send_message("service_restart", kwargs={"name": "clever-show"})
if not self.call_config_dialog(cfg, save_callback, restart_callback, f"{client.copter_id}"):
return False
return True
def call_config_dialog(self, cfg: config.ConfigManager, on_save=None, on_restart=None, name="Untitled.ini"):
self.setupModel(cfg.full_dict(include_defaults=True), convert_types=(not cfg.validated))
self.ui.do_restart.setEnabled(on_restart is not None)
self._filename = name
self.update_title()
if not self.validation_loop(cfg, cfg.config.configspec):
return False
if on_save is not None:
on_save()
if on_restart is not None and self.ui.do_restart.isChecked():
on_restart()
return True
@classmethod
def call_standalone_dialog(cls):
dialog = cls()
dialog._call_standalone_dialog()
def _call_standalone_dialog(self):
path = QFileDialog.getOpenFileName(self, "Select configuration or specification file",
filter="Config and spec files (*.ini)")[0]
if not path:
return False
cfg = config.ConfigManager()
try:
cfg.load_from_file(path)
except ValueError as error: # When file do not exist or not validated properly
QMessageBox.warning(self, "Error while opening file!",
"Config cannot be opened or validated: {}".format(error))
return False
def save_callback():
if cfg.config.filename is None:
save_path = QFileDialog.getSaveFileName(self, "Save configuration file",
directory=self.filename,
filter="Config files (*.ini)")[0]
if not save_path:
return False
else:
save_path = cfg.config.filename
cfg.config.filename = save_path
cfg.write()
if cfg.config.filename is not None:
name = os.path.split(cfg.config.filename)[1]
else: # when editing only configspec-based file
name = os.path.split(path)[1]
if not self.call_config_dialog(cfg, on_save=save_callback, name=name):
return False
return True
if __name__ == '__main__':
def except_hook(cls, exception, traceback):
print(cls, exception, traceback)
sys.__excepthook__(cls, exception, traceback)
sys.excepthook = except_hook
app = QtWidgets.QApplication(sys.argv)
ui = ConfigDialog()
ui.call_standalone_dialog()
# d = {'section': {'opt': 1, "opt222": 'text'}}
# ui.setupModel(d, pure_dict=True)
# ui.show()
# app.exec()
# print(ui.model.to_config_dict())

557
server/copter_table.py Normal file
View File

@@ -0,0 +1,557 @@
from functools import partial
from copy import deepcopy
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import Qt as Qt, QObject, QEvent, QModelIndex
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QCursor
from PyQt5.QtWidgets import QTableView, QMessageBox, QMenu, QAction, QWidgetAction, QListWidget, \
QAbstractItemView, QListWidgetItem, QVBoxLayout, QHBoxLayout, QPushButton, QInputDialog, QLineEdit, QApplication
from config_editor_models import ConfigDialog
import copter_table_models as table
def save_preset(config, current, header_dict):
presets = config.table_presets
for key in presets[HeaderEditWidget.default]:
if key not in presets[current] and not header_dict[key][0]:
header_dict.pop(key)
presets[current] = header_dict
# config.write()
class HeaderViewFilter(QObject):
def __init__(self, parent, header, *args):
super().__init__(parent, *args)
self.header = header
self._parent = parent
def eventFilter(self, object, event):
if event.type() == QEvent.Enter:
# logicalIndex = self.header.logicalIndexAt(event.pos())
self.parent().cellHover.emit(QModelIndex())
else:
return False
return True
class CopterTableWidget(QTableView):
override_cursors = {
"copter_id": Qt.IBeamCursor,
"config_version": Qt.OpenHandCursor,
"selfcheck": Qt.PointingHandCursor,
}
cellHover = QtCore.pyqtSignal(QModelIndex)
cellEntered = QtCore.pyqtSignal(int, int)
cellExited = QtCore.pyqtSignal(int, int)
def __init__(self, model: table.CopterDataModel, config):
QTableView.__init__(self)
self.config = config
self.model = model
self.proxy_model = table.CopterProxyModel()
self.proxy_model.setSourceModel(self.model)
self.proxy_model.setDynamicSortFilter(True)
# Initiate table and table self.model
self.setModel(self.proxy_model)
self.columns = self.model.columns # [header.strip() for header in self.model.headers] # header keys
self.current_columns = self.columns[:]
self._last_hover_index = QtCore.QModelIndex()
self._previous_cursor = None
self.cellHover.connect(self.cell_hover)
self.cellExited.connect(self.cell_exited)
self.cellEntered.connect(self.cell_entered)
header = self.horizontalHeader()
self.filter = HeaderViewFilter(self, header)
header.installEventFilter(self.filter)
header.setCascadingSectionResizes(False)
header.setStretchLastSection(True)
header.setSectionsMovable(True)
header.sectionMoved.connect(self.moved)
header.setContextMenuPolicy(Qt.CustomContextMenu)
header.customContextMenuRequested.connect(self.showHeaderMenu)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.open_menu)
# Adjust properties
self.setTextElideMode(QtCore.Qt.ElideMiddle)
self.setWordWrap(True)
self.setSortingEnabled(True)
self.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
self.resizeColumnsToContents()
self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems)
self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.doubleClicked.connect(self.on_double_click)
self.setDragDropMode(QAbstractItemView.DragDrop)
self.setMouseTracking(True)
def mousePressEvent(self, event):
super().mousePressEvent(event)
index = self.indexAt(event.pos())
if index.column() == -1 and index.row() == -1:
self.clearSelection()
def mouseMoveEvent(self, event):
self.cell_hover(self.indexAt(event.pos()))
super().mouseMoveEvent(event)
def leaveEvent(self, event):
self.cell_hover(QtCore.QModelIndex())
def dragEnterEvent(self, *args, **kwargs):
self.cell_hover(QtCore.QModelIndex())
super().dragEnterEvent(*args, **kwargs)
def cell_hover(self, index):
if index != self._last_hover_index:
self.cellExited.emit(self._last_hover_index.row(), self._last_hover_index.column())
self.cellEntered.emit(index.row(), index.column())
self._last_hover_index = QtCore.QPersistentModelIndex(index)
@pyqtSlot(int, int)
def cell_entered(self, row, column):
if column != -1 and self.columns[column] in self.override_cursors:
self._previous_cursor = QApplication.overrideCursor()
if self._previous_cursor is None:
QApplication.setOverrideCursor(self.override_cursors[self.columns[column]])
@pyqtSlot(int, int)
def cell_exited(self, row, column):
# if self._previous_cursor is not None:
# QApplication.setOverrideCursor(self._previous_cursor)
if self._previous_cursor is None:
QApplication.restoreOverrideCursor()
def moved(self, logical_index, old_index, new_index):
name = self.current_columns.pop(old_index)
self.current_columns.insert(new_index, name)
def set_column_order(self, order):
if set(order) != set(self.current_columns):
raise ValueError
for index_to, item in enumerate(order):
index_from = self.current_columns.index(item)
if index_to != index_from:
self.horizontalHeader().moveSection(index_from, index_to)
def load_columns(self, item_dict: dict = None):
presets = self.config.table_presets
if item_dict is None:
item_dict = presets[self.config.table_presets_current]
item_dict.update({key: (False, presets[HeaderEditWidget.default][key][1])
for key in presets[HeaderEditWidget.default] if key not in item_dict})
self.set_column_order(item_dict.keys())
# self.set_column_widths({key: val[1] for key, val in item_dict.items()})
for name, value in item_dict.items(): # for index, name in enumerate(self.columns):
index = self.columns.index(name)
show, width = value
self.setColumnHidden(index, not show) # self.setColumnHidden(index, not item_dict.get(name, False))
self.setColumnWidth(index, width)
def _get_column_item(self, column):
index = self.columns.index(column)
presets = self.config.table_presets
show = not self.isColumnHidden(index)
# columnWidth is 0 when hidden, trying to get previous width from config or default
width = self.columnWidth(index) or \
presets[self.config.table_presets_current].get(column, 0)[1] or \
presets[HeaderEditWidget.default][column][1]
return show, width
@property
def item_dict(self):
return {column: self._get_column_item(column) for column in self.current_columns}
def save_columns(self):
current = self.config.table_presets_current
header_dict = self.item_dict
save_preset(self.config, current, header_dict)
def select_all(self, state):
for i in range(self.model.rowCount()):
self.model.update_data(i, 0, state, Qt.CheckStateRole)
def toggle_select(self):
if len(list(self.model.user_selected())) == self.model.rowCount(): # if all items are selected
state = Qt.Unchecked
else:
state = Qt.Checked
self.select_all(state)
@pyqtSlot(QtCore.QModelIndex)
def on_double_click(self, index):
if self.model.is_column(index, "selfcheck"):
data = self.proxy_model.data(index, role=table.ModelDataRole)
if data and data != "OK":
self._show_info("Selfcheck info", data)
def _show_info(self, title, data):
dialog = QMessageBox()
dialog.setIcon(QMessageBox.NoIcon)
dialog.setStandardButtons(QMessageBox.Ok)
dialog.setWindowTitle(title)
dialog.setText("\n".join(data[:10]))
dialog.setDetailedText("\n".join(data))
dialog.exec()
def showHeaderMenu(self, event):
self.save_columns()
menu = QMenu(self)
header_view = HeaderEditWidget(self, self.config, menu_mode=True, parent=menu)
# header_view.setFixedHeight((header_view.geometry().height()-2) * len(header_view.columns))
# box.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
action = QWidgetAction(menu)
action.setDefaultWidget(header_view)
menu.addAction(action)
menu.exec_(QCursor.pos())
header_view.save_preset()
@pyqtSlot(QtCore.QPoint)
def open_menu(self, point):
menu = QMenu(self)
id = self.indexAt(point).siblingAtColumn(0).data()
item = self.model.get_row_by_attr('copter_id', id)
edit_config = QAction("Edit config")
edit_config.triggered.connect(partial(self.edit_copter_config, item))
menu.addAction(edit_config)
copy_config = QAction("Copy config to selected")
copy_config.triggered.connect(partial(self.copy_config, item))
menu.addAction(copy_config)
if item is None:
edit_config.setDisabled(True)
copy_config.setDisabled(True)
menu.exec_(QCursor.pos())
@pyqtSlot()
def edit_copter_config(self, copter):
dialog = ConfigDialog()
copter.client.get_response("config", dialog.call_copter_dialog, request_kwargs={'send_configspec': True})
@pyqtSlot()
def copy_config(self, copter):
def send_callback(client, value):
config = value["config"]
config.pop("PRIVATE", None) # delete private section
for _copter in self.model.user_selected():
if _copter.client is client:
continue # don't send config back to the same copter
_copter.client.send_message("config", kwargs={"config": config, "mode": "modify"})
copter.client.get_response("config", send_callback, request_kwargs={'send_configspec': False})
# def _selfcheck_shortener(self, data): # TODO!!!
# shortened = []
# for line in data:
# if len(line) > 89:
# pass
# return shortened
class HeaderListWidget(QListWidget):
ColumnKeyRole = Qt.UserRole + 1000
ColumnWidthRole = Qt.UserRole + 1001
dropped = QtCore.pyqtSignal(bool)
def __init__(self, parent=None, default_items=None):
super().__init__(parent)
if default_items is not None:
self.populate_items(default_items)
self.setDragDropMode(QAbstractItemView.InternalMove)
self.setDefaultDropAction(Qt.MoveAction)
def populate_items(self, item_dict: dict):
self.clear()
for name, value in item_dict.items():
visible, width = value
flags = Qt.ItemIsUserCheckable | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | Qt.ItemIsEnabled
state = Qt.Checked if visible else Qt.Unchecked
item = QListWidgetItem(table.CopterDataModel.columns_dict.get(name, "").strip() or name, self)
item.setFlags(flags)
item.setCheckState(state)
item.setData(self.ColumnKeyRole, name)
item.setData(self.ColumnWidthRole, width)
@property
def item_dict(self):
return {self.item(i).data(self.ColumnKeyRole):
(bool(self.item(i).checkState()), self.item(i).data(self.ColumnWidthRole))
for i in range(self.count())}
def dropEvent(self, event: QtGui.QDropEvent):
super().dropEvent(event)
self.dropped.emit(True)
class ActiveHeaderListWidget(HeaderListWidget):
def __init__(self, source: CopterTableWidget, parent=None):
super().__init__(parent=parent)
self.source_widget = source
self.current_columns = source.current_columns
self.columns = source.columns
self._populate_from_widget()
self.itemChanged.connect(self.on_itemChanged)
def _populate_from_widget(self):
self.populate_items(self.source_widget.item_dict)
@pyqtSlot(QListWidgetItem)
def on_itemChanged(self, item):
key = item.data(HeaderListWidget.ColumnKeyRole)
if key is None:
return
self.source_widget.setColumnHidden(self.columns.index(key), not bool(item.checkState()))
def dropEvent(self, event: QtGui.QDropEvent):
super().dropEvent(event)
column_order = [self.item(i).data(HeaderListWidget.ColumnKeyRole) for i in range(self.count())]
self.source_widget.set_column_order(column_order)
class HeaderEditWidget(QtWidgets.QWidget):
add_new_text = "< add new >"
default = "DEFAULT"
saved_signal = QtCore.pyqtSignal(bool)
def __init__(self, source, config, menu_mode=False, *args, **kwargs):
super().__init__(*args, **kwargs)
# self.auto_apply = auto_apply
self.source = source # source = copter table
self.config = config
self.menu_mode = menu_mode
self.preset_widget = QtWidgets.QComboBox()
self.header_widget = ActiveHeaderListWidget(self.source) \
if self.menu_mode else HeaderListWidget()
# self.header_widget.itemChanged.connect(partial(self.saved_signal.emit, False))
self.header_widget.model().dataChanged.connect(partial(self.saved_signal.emit, False))
self.header_widget.dropped.connect(partial(self.saved_signal.emit, False))
self.previous = self.config.table_presets_current
self.save = True
self.setupUi()
@pyqtSlot()
def call_dialog(self):
self.save_preset()
self.save = False
HeaderEditDialog(self.source, self.config).exec()
def setupUi(self):
self.header_widget.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.update_preset_list()
self.preset_widget.currentTextChanged.connect(self.on_preset_changed)
self.on_preset_changed(self.previous) # to init
vbox = QVBoxLayout()
vbox.addWidget(self.header_widget)
vbox.addWidget(self.preset_widget)
hbox = QHBoxLayout()
if not self.menu_mode:
add_button = QPushButton("Add")
add_button.clicked.connect(self.add_preset)
remove_button = QPushButton("Remove")
remove_button.setToolTip("Permanently remove preset from config")
remove_button.clicked.connect(self.remove_preset)
save_button = QPushButton("Save")
save_button.clicked.connect(self.save_preset)
apply_button = QPushButton("Apply")
apply_button.clicked.connect(self.apply_preset)
apply_button.setDefault(True)
apply_button.setFocus()
hbox.addWidget(add_button)
hbox.addWidget(remove_button)
hbox.addStretch()
hbox.addWidget(save_button)
hbox.addWidget(apply_button)
else:
dialog_button = QPushButton("Manage presets")
dialog_button.clicked.connect(self.call_dialog)
hbox.addWidget(dialog_button)
vbox.addLayout(hbox)
self.setLayout(vbox)
def update_preset_list(self):
self.preset_widget.clear()
for name, preset in self.config.table_presets.items():
if isinstance(preset, dict): # looking only for preset sections
self.preset_widget.addItem(name)
self.preset_widget.addItem(self.add_new_text)
self.preset_widget.setCurrentText(self.previous)
def on_preset_changed(self, index):
if not index:
return
if index == self.add_new_text:
self.add_preset()
return
self.previous = index
presets = self.config.table_presets
item_dict = {key: value for key, value in presets[index].items()}
item_dict.update({key: (False, presets[self.default][key][1])
for key in presets[self.default] if key not in item_dict})
if self.menu_mode:
self.source.set_column_order(list(item_dict.keys())) # hidden\shown is hold by header widget's itemChanged
for name, value in item_dict.items():
self.source.setColumnWidth(self.source.columns.index(name), value[1])
self.config.table_presets_current = index
self.header_widget.populate_items(item_dict)
self.saved_signal.emit(True)
def add_preset(self):
name, ok = QInputDialog.getText(None, "Enter new preset name", "Name:",
QLineEdit.Normal, "")
if not ok or not name:
self.preset_widget.setCurrentText(self.previous)
return
name = name.strip()
if name in self.config.table_presets or name == self.default or name == self.add_new_text:
QMessageBox.warning(None, "Preset already exists!", "Preset already exists!")
self.preset_widget.setCurrentText(self.previous)
return
self.config.table_presets[name] = deepcopy(dict(self.config.table_presets[self.default]))
# self.config.write()
self.update_preset_list()
self.preset_widget.setCurrentText(name)
def remove_preset(self):
if self.preset_widget.currentText() == self.default:
QMessageBox.warning(None, "Can't delete default preset!", "Can't delete default preset!")
return
reply = QMessageBox.question(None, "Action can't be undone", "Remove anyway?",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply != QMessageBox.Yes:
return
self.config.table_presets.pop(self.preset_widget.currentText())
# self.config.write()
self.previous = self.default
self.update_preset_list()
@pyqtSlot()
def save_preset(self):
if not self.save: # don't save after calling dialog to avoid overrides
return
current = self.preset_widget.currentText()
header_dict = self.header_widget.item_dict
save_preset(self.config, current, header_dict)
self.saved_signal.emit(True)
@pyqtSlot()
def apply_preset(self):
self.config.table_presets_current = self.preset_widget.currentText()
self.save_preset()
self.source.load_columns()
class HeaderEditDialog(QtWidgets.QDialog):
def __init__(self, source, config, parent=None):
super(HeaderEditDialog, self).__init__(parent=None)
self.widget = HeaderEditWidget(source, config, menu_mode=False)
self.setupUI()
self.unsaved = False
self.widget.saved_signal.connect(self.update_title)
self.update_title(True)
def setupUI(self):
layout = QVBoxLayout()
layout.addWidget(self.widget)
self.setLayout(layout)
@pyqtSlot(bool)
def update_title(self, saved):
unsaved = not saved
self.unsaved = unsaved
self.setWindowTitle(f"Column preset editor - {self.widget.preset_widget.currentText()}"
+ "*" * unsaved)
def closeEvent(self, event):
if not self.unsaved:
event.accept()
return
reply = QMessageBox.question(self, "Confirm exit", "There are unsaved changes in current preset. "
"Are you sure you want to exit?",
QMessageBox.No | QMessageBox.Yes, QMessageBox.No)
if reply != QMessageBox.Yes:
event.ignore()
else:
event.accept()
if __name__ == '__main__':
import sys
def except_hook(cls, exception, traceback):
sys.__excepthook__(cls, exception, traceback)
sys.excepthook = except_hook # for debugging (exceptions traceback)
app = QtWidgets.QApplication(sys.argv)
import copter_table_models
model = copter_table_models.CopterDataModel()
# for i in range(10):
# model.add_client(copter_table_models.StatedCopterData())
import config
c = config.ConfigManager()
c.load_config_and_spec("config\server.ini")
# print(c.config)
# print(c._name_dict)
w1 = CopterTableWidget(model, c)
w = HeaderEditWidget(w1, c)
print(w1.item_dict)
# print(*w1.current_columns, sep='\n')
# w.show()
app.exec()

View File

@@ -0,0 +1,723 @@
import os
import re
import sys
import math
import time
import subprocess
from contextlib import suppress
from functools import partialmethod
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt as Qt, QUrl, QDir
from config import ConfigManager
# Additional custom roles to interact with various table data
ModelDataRole = 998
ModelStateRole = 999
def get_git_version(): # TODO import from animation
try:
return subprocess.check_output("git log --pretty=format:'%h' -n 1", shell=True).decode('UTF-8')
except subprocess.CalledProcessError: # when no git repository info present
return None # todo probably add special file
class CheckState:
def __init__(self, bool_state, color):
self._bool = bool_state
self.color = color
self.brush = QtGui.QBrush(self.color)
def __bool__(self):
return self._bool
# State objects providing both boolean and color information for table
# Add more if required
true_state = CheckState(True, Qt.green)
false_state = CheckState(False, Qt.red)
missing_state = CheckState(False, Qt.yellow)
outdated_state = CheckState(False, Qt.magenta)
class ModelChecks:
checks_dict = {}
battery_min = 50.0
start_pos_delta_max = 1.0
time_delta_max = 1.0
check_current_pos = True
check_git = True
@classmethod
def column_check(cls, column, pass_context=False):
def inner(f):
def wrapper(item, context=None):
if item is None:
return None
if pass_context:
return f(item, context)
return f(item)
cls.checks_dict[column] = wrapper
return wrapper
return inner
@classmethod
def check(cls, column, context):
if isinstance(column, int):
column = context.columns[column]
item = context[column]
try:
return cls.checks_dict[column](item, context)
except KeyError: # When there is no check
return None if item is None else true_state # item is not None
@ModelChecks.column_check("git_version")
def check_ver(item):
if not ModelChecks.check_git:
return True
version = get_git_version()
if version is not None:
return version == item
return True
@ModelChecks.column_check("animation_info")
def check_anim(item):
if item:
return str(item[1]) == 'OK'
else:
return False
@ModelChecks.column_check("battery")
def check_bat(item):
if item == "NO_INFO":
return False
return item[1] * 100 > ModelChecks.battery_min
@ModelChecks.column_check("fcu_status")
def check_sys_status(item):
return item == "STANDBY"
@ModelChecks.column_check("calibration_status")
def check_cal_status(item):
return item == "OK"
@ModelChecks.column_check("mode")
def check_mode(item):
return (item != "NO_FCU") and not ("CMODE" in item)
@ModelChecks.column_check("selfcheck")
def check_selfcheck(item):
return item == "OK"
@ModelChecks.column_check("current_position")
def check_pos(item):
if not ModelChecks.check_current_pos:
return True
if item == 'NO_POS':
return False
return not math.isnan(item[0])
# @ModelChecks.column_check("last_task")
# def check_task(item):
# return True
@ModelChecks.column_check('time_delta')
def check_time_delta(item):
return abs(item) < ModelChecks.time_delta_max
@ModelChecks.column_check("start_position", pass_context=True)
def check_start_pos(item, context):
if len(item) == 6:
if not item[4] in ["takeoff", "fly"]:
return False
if ModelChecks.start_pos_delta_max == 0:
return True
if context.current_position is None:
return item != 'NO_POS' # maybe should return true
delta = get_distance(get_position(context.current_position),
get_position(context.start_position))
if math.isnan(delta):
return False
return delta < ModelChecks.start_pos_delta_max
def get_position(position):
if not isinstance(position, str) and position[0] != float('nan'):
return position[:3]
return [float('nan')] * 3
def get_distance(pos1, pos2): # todo as common function
if any(math.isnan(x) for x in pos1 + pos2):
return float('nan')
return math.sqrt(sum(map(lambda p: (p[0] - p[1]) ** 2, zip(pos1, pos2)))) # point distance formula
class CopterData:
def __init__(self, columns=(), **kwargs):
self.columns = columns
for column in columns:
setattr(self, column, None)
for attr, value in kwargs.items():
setattr(self, attr, value)
def __getitem__(self, key):
if key in self.columns:
return getattr(self, key)
return getattr(self, self.columns[key])
def __setitem__(self, key, value):
if key in self.columns:
setattr(self, key, value)
else:
setattr(self, self.columns[key], value)
def __repr__(self):
return str({key: self[key] for key in self.columns})
class StatedCopterData(CopterData):
def __init__(self, columns=(), checks_defaults=None, checks_class=ModelChecks, **kwargs):
if checks_defaults is None:
checks_defaults = {}
self.__dict__['states'] = CopterData(columns, **checks_defaults)
self.__dict__['checks'] = checks_class
self.__dict__['all_checks'] = None
super().__init__(columns, **kwargs)
def __setattr__(self, key, value):
self.__dict__[key] = value
if key in self.columns:
with suppress(KeyError):
self.states.__dict__[key] = \
self.checks.check(key, self)
self.states.__dict__["all_checks"] = all([self.states[i] for i in self.checks.checks_dict.keys()])
class ModelFormatter:
view_formatters = {}
place_formatters = {}
VIEW_FORMATTER = 1
PLACE_FORMATTER = 2
@classmethod
def get_formatter(cls, formatter_type):
if formatter_type == cls.PLACE_FORMATTER:
return cls.place_formatters
if formatter_type == cls.VIEW_FORMATTER:
return cls.view_formatters
raise ValueError('Unknown formatter type')
@classmethod
def format(cls, column, value, formatter_type):
formatters_dict = cls.get_formatter(formatter_type)
if isinstance(column, int):
column = CopterDataModel.columns[column]
try:
return formatters_dict[column](value)
except KeyError:
return value # when there is no formatter for the column
format_place = partialmethod(format, formatter_type=PLACE_FORMATTER)
format_view = partialmethod(format, formatter_type=VIEW_FORMATTER)
@classmethod
def column_formatter(cls, column, formatter_type):
def inner(f):
formatters_dict = cls.get_formatter(formatter_type)
formatters_dict[column] = f
def wrapper(value):
return f(value)
return wrapper
return inner
place_formatter = partialmethod(column_formatter, formatter_type=PLACE_FORMATTER)
view_formatter = partialmethod(column_formatter, formatter_type=VIEW_FORMATTER)
@ModelFormatter.place_formatter("copter_id")
def place_id(value):
value = str(value).strip()
# 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.view_formatter("animation_info")
def view_animation_info(value):
try:
id, state = value
except ValueError:
return ""
else:
if state == 'OK':
return id
else:
return state
@ModelFormatter.place_formatter("battery")
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.view_formatter("battery")
def view_battery(value):
if isinstance(value, list):
battery_v, battery_p = value
return f"{battery_v:4.1f}V {min(battery_p, 1):4.0%}"
return value
@ModelFormatter.view_formatter("selfcheck")
def view_selfcheck(value):
if isinstance(value, list):
if len(value) == 1 and len(value[0]) <= 8:
return value[0]
return "ERROR"
return value
@ModelFormatter.view_formatter("current_position")
def view_current_position(value):
if isinstance(value, list):
x, y, z, yaw, frame = value
return f"{x: .2f} {y: .2f} {z: .2f} {yaw: .0f} {frame}"
return value
@ModelFormatter.view_formatter("start_position")
def view_start_position(value):
if isinstance(value, list):
x, y, z, yaw, action, delay = value
if action in ['fly', 'takeoff']:
return f"{x: .2f} {y: .2f} {z: .2f} {yaw: .0f} {action} {delay: .1f}"
else:
return f"{action}"
return value
@ModelFormatter.place_formatter("last_task")
def place_last_task(value):
if value is None: # TODO possible behaviour deviation
return 'No task'
return value
@ModelFormatter.place_formatter("time_delta")
def place_time_delta(value):
return abs(value - time.time())
@ModelFormatter.view_formatter("time_delta")
def view_time_delta(value):
return f"{value:.3f}"
class CopterDataModel(QtCore.QAbstractTableModel):
columns_dict = {'copter_id': 'copter ID',
'git_version': 'version',
'config_version': 'configuration',
'animation_info': 'animation ID',
'battery': ' battery ',
'fcu_status': 'FCU status',
'calibration_status': 'sensors',
'mode': ' mode ',
'selfcheck': ' checks ',
'current_position': 'current x y z yaw frame_id',
'start_position': 'start x y z yaw action delay',
'last_task': 'last task',
'time_delta': 'dt',
}
columns = list(columns_dict.keys())
selected_ready_signal = QtCore.pyqtSignal(bool)
selected_takeoff_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)
update_data_signal = QtCore.pyqtSignal(int, int, QtCore.QVariant, QtCore.QVariant)
add_client_signal = QtCore.pyqtSignal(object)
remove_row_signal = QtCore.pyqtSignal(int)
remove_client_signal = QtCore.pyqtSignal(object)
def __init__(self, checks=ModelChecks, formatter=ModelFormatter, data_model=StatedCopterData, parent=None):
super(CopterDataModel, self).__init__(parent)
self.headers = list(self.columns_dict.values())
self.data_contents = []
self.checks = checks
self.formatter = formatter
self.data_model = data_model
self.update_data_signal.connect(self._update_data)
self.add_client_signal.connect(self._add_client)
self.remove_row_signal.connect(self._remove_row)
self.remove_client_signal.connect(self._remove_row_data)
def insertRows(self, contents, position='last', parent=QtCore.QModelIndex()):
rows = len(contents)
position = len(self.data_contents) if position == 'last' else position
self.beginInsertRows(parent, position, position + rows - 1)
self.data_contents[position:position] = contents
self.endInsertRows()
def removeRows(self, position, rows=1, index=QtCore.QModelIndex()):
self.beginRemoveRows(QtCore.QModelIndex(), position, position + rows - 1)
self.data_contents = self.data_contents[:position] + self.data_contents[position + rows:]
self.endRemoveRows()
self.emit_signals()
return True
@classmethod
def is_column(cls, index, column_name):
return index.column() == cls.columns.index(column_name)
def user_selected(self, contents=()):
return self.filter(lambda x: x.states.checked == Qt.Checked, contents)
def filter(self, f, contents=()):
contents = contents or self.data_contents
return filter(f, contents)
def selected_check(self, f, selected=()):
selected = selected or set(self.user_selected())
return bool(selected) and all(f(item) for item in selected) # selected.issubset(self.filter(f))
def get_row_data(self, index):
row = index.row()
if row == -1:
return None
try:
return self.data_contents[row]
except IndexError:
return None
def get_row_index(self, row_data):
try:
return self.data_contents.index(row_data)
except ValueError:
return None
def get_row_by_attr(self, attr, value):
try:
return next(filter(lambda x: getattr(x, attr, None) == value, self.data_contents))
except StopIteration:
return None
def rowCount(self, n=None):
return len(self.data_contents)
def columnCount(self, n=None):
return len(self.headers)
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
return self.headers[section]
def data(self, index, role=Qt.DisplayRole):
row = index.row()
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(self.formatter.format_view(self.columns[col], item)) if item is not None else ""
elif role == ModelDataRole:
return self.data_contents[row][col]
elif role == Qt.BackgroundRole:
state = self.data_contents[row].states[col]
if state is None:
state = missing_state
elif isinstance(state, bool):
state = true_state if state else false_state
return state.brush
elif role == Qt.CheckStateRole and col == 0:
return self.data_contents[row].states.checked
if role == QtCore.Qt.TextAlignmentRole and col != 0:
return QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter
def emit_signals(self):
selected = set(self.user_selected())
self.selected_ready_signal.emit(self.selected_check(lambda x: x.states.all_checks, selected))
self.selected_takeoff_ready_signal.emit(self.selected_check(takeoff_checks, selected))
self.selected_flip_ready_signal.emit(self.selected_check(flip_checks, selected))
self.selected_calibrating_signal.emit(self.selected_check(calibrating_check, selected))
self.selected_calibration_ready_signal.emit(self.selected_check(calibration_ready_check, selected))
@QtCore.pyqtSlot()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid():
return False
col = index.column()
row = index.row()
if role == Qt.CheckStateRole:
self.data_contents[row].states.checked = value
elif role == Qt.EditRole: # For user/outer actions with data, place modifiers applied
formatted_value = self.formatter.format_place(self.columns[col], value)
if formatted_value is None: # todo use new := syntax
return False
self.data_contents[row][col] = formatted_value
if col == 0:
self.data_contents[row].client.send_message("id", kwargs={"new_id": formatted_value})
elif role == ModelDataRole: # For inner setting\editing of raw data
self.data_contents[row][col] = value
elif role == ModelStateRole:
self.data_contents[row].states[col] = value
else:
return False
self.emit_signals()
self.dataChanged.emit(index, index, (role,))
return True
def flags(self, index):
roles = Qt.ItemIsSelectable | Qt.ItemIsEnabled
if index.column() == 0:
roles |= Qt.ItemIsUserCheckable | Qt.ItemIsEditable
if self.is_column(index, "config_version"):
roles |= Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled
return roles
def supportedDropActions(self):
return Qt.CopyAction | Qt.MoveAction
def mimeTypes(self):
return ['text/uri-list']
def mimeData(self, indexes):
index = indexes[0]
if self.is_column(index, "config_version"):
return self._config_mime(index)
return None
def _config_mime(self, index):
mimedata = QtCore.QMimeData()
path = os.path.join(QDir.tempPath(), "config_{}.ini".format(
self.data_contents[index.row()].copter_id))
with suppress(OSError): # remove if file exists
os.remove(path)
self.data_contents[index.row()].client.get_file("config/client.ini", path, )
mimedata.setData("application/copter_row_info",
bytes(self.data_contents[index.row()].copter_id, encoding="UTF-8"))
mimedata.setUrls([QUrl.fromLocalFile(path)])
return mimedata
def dropMimeData(self, mimedata, action, row, column, index):
if action == Qt.IgnoreAction:
return True
if self.is_column(index, "config_version"):
if not mimedata.hasUrls():
return False
if str(mimedata.data("application/copter_row_info")) == self.data_contents[index.row()].copter_id:
return False # to protect from dropping to the same cell
# print(mimedata.hasUrls(), mimedata.urls, mimedata.formats())
return self.drop_config(mimedata.urls()[0].toLocalFile(), index.row())
return True
def drop_config(self, path, row):
if not ConfigManager.config_exists(path):
return False
config = ConfigManager()
config.load_only_config(path)
config_dict = config.full_dict(include_defaults=False)
config_dict.pop("PRIVATE", None)
self.data_contents[row].client.send_message("config", kwargs={
"config": config_dict, "mode": "rewrite"})
return False
# Thread-safe wrappers
def add_client(self, **kwargs):
default_states = {"checked": 0, "copter_id": True}
self.add_client_signal.emit(self.data_model(self.columns, default_states, **kwargs))
def remove_client_data(self, row_data):
self.remove_client_signal.emit(row_data)
def remove_row(self, row):
self.remove_row_signal.emit(row)
def update_data(self, row, col, data, role=ModelDataRole):
self.update_data_signal.emit(row, col, data, role)
@QtCore.pyqtSlot(int, int, QtCore.QVariant, QtCore.QVariant)
def _update_data(self, row, col, value, role=Qt.EditRole):
self.setData(self.index(row, col), value, role)
@QtCore.pyqtSlot(object)
def _add_client(self, client):
self.insertRows([client])
@QtCore.pyqtSlot(int) # Probably deprecated now
def _remove_row(self, row):
self.removeRows(row)
@QtCore.pyqtSlot(object)
def _remove_row_data(self, data):
row = self.get_row_index(data)
if row is not None:
self.removeRows(row)
def check_checklist(copter_item, checklist=()):
return all(copter_item.states[col] for col in checklist)
def takeoff_checks(copter_item):
checklist = ("battery", "fcu_status", "mode", "selfcheck", "current_position")
return check_checklist(copter_item, checklist)
def flip_checks(copter_item):
checklist = ("battery", "mode", "current_position")
if not check_checklist(copter_item, checklist):
return False
if copter_item["fcu_status"] != "ACTIVE":
return False
return True
# for col in checklist:
# if not copter_item.state[col]: # ModelChecks.check(col, copter_item):
# return False
def calibrating_check(copter_item):
return copter_item["calibration_status"] == "CALIBRATING"
def calibration_ready_check(copter_item):
if not copter_item.states["fcu_status"]: # ModelChecks.check("fcu_status", copter_item):
return False
return not calibrating_check(copter_item)
class CopterProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, parent=None):
super(CopterProxyModel, self).__init__(parent)
@staticmethod
def human_sort_prepare(item):
if item:
item = [int(x) if x.isdigit() else x.lower() for x in re.split('([0-9]+)', str(item))]
else:
item = []
return item
def lessThan(self, left, right):
leftData = self.sourceModel().data(left)
rightData = self.sourceModel().data(right)
return self.human_sort_prepare(leftData) < self.human_sort_prepare(rightData)
if __name__ == '__main__':
import time
def timer():
idc = 1001
while True:
myModel.setData(myModel.index(0, 0), idc)
idc += 1
time.sleep(1)
app = QtWidgets.QApplication.instance()
if app is None:
app = QtWidgets.QApplication(sys.argv)
tableView = QtWidgets.QTableView()
myModel = CopterDataModel()
proxyModel = CopterProxyModel()
proxyModel.setDynamicSortFilter(True)
proxyModel.setSourceModel(myModel)
tableView.setModel(proxyModel)
tableView.verticalHeader().hide()
tableView.setSortingEnabled(True)
tableView.show()
msgs = []
msg = "[{}]: Failure: {}".format("FCU connection", "Angular velocities estimation is not available")
msgs.append(msg)
msg = "[{}]: Failure: {}".format("FCU connection1", "Angular velocities estimation is not available")
msgs.append(msg)
msg = "[{}]: Failure: {}".format("FCU connection2", "Angular velocities estimation is not available")
msgs.append(msg)
# myModel._add_client(StatedCopterData(copter_id=1000, checked=0, selfcheck=msgs, time_utc=1))
# myModel._add_client(StatedCopterData(checked=2, selfcheck="OK", time_utc=2))
# myModel._add_client(StatedCopterData(checked=2, selfcheck="not ok", time_utc="no"))
myModel.add_client(copter_id=1000, client=None, git_version='11318ca', selfcheck=msgs)
# myModel.setData(myModel.index(0, 1), "test")
# t = threading.Thread(target=timer, daemon=True)
# t.start()
print(QtCore.QT_VERSION_STR)
print(get_git_version())
myModel.update_data(0, 3, [1, 2], role=Qt.EditRole)
app.exec_()

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
server/icons/image.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

431
server/server_gui.py Normal file
View File

@@ -0,0 +1,431 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'server_gui.ui'
#
# Created by: PyQt5 UI code generator 5.13.0
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1360, 869)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setEnabled(True)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize)
self.horizontalLayout.setObjectName("horizontalLayout")
self.tableView = QtWidgets.QTableView(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.tableView.sizePolicy().hasHeightForWidth())
self.tableView.setSizePolicy(sizePolicy)
self.tableView.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
self.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems)
self.tableView.setTextElideMode(QtCore.Qt.ElideMiddle)
self.tableView.setSortingEnabled(True)
self.tableView.setWordWrap(True)
self.tableView.setObjectName("tableView")
self.tableView.horizontalHeader().setCascadingSectionResizes(False)
self.tableView.horizontalHeader().setDefaultSectionSize(50)
self.tableView.horizontalHeader().setMinimumSectionSize(50)
self.tableView.horizontalHeader().setStretchLastSection(True)
self.tableView.verticalHeader().setVisible(False)
self.horizontalLayout.addWidget(self.tableView)
self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize)
self.verticalLayout.setObjectName("verticalLayout")
self.formLayout = QtWidgets.QFormLayout()
self.formLayout.setLabelAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.formLayout.setFormAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop)
self.formLayout.setObjectName("formLayout")
self.start_text = QtWidgets.QLabel(self.centralwidget)
self.start_text.setLayoutDirection(QtCore.Qt.RightToLeft)
self.start_text.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
self.start_text.setObjectName("start_text")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.start_text)
self.start_delay_spin = QtWidgets.QSpinBox(self.centralwidget)
self.start_delay_spin.setObjectName("start_delay_spin")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.start_delay_spin)
self.music_delay_spin = QtWidgets.QDoubleSpinBox(self.centralwidget)
self.music_delay_spin.setDecimals(1)
self.music_delay_spin.setMaximum(1000.0)
self.music_delay_spin.setObjectName("music_delay_spin")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.music_delay_spin)
self.music_checkbox = QtWidgets.QCheckBox(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.music_checkbox.sizePolicy().hasHeightForWidth())
self.music_checkbox.setSizePolicy(sizePolicy)
self.music_checkbox.setFocusPolicy(QtCore.Qt.NoFocus)
self.music_checkbox.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
self.music_checkbox.setLayoutDirection(QtCore.Qt.LeftToRight)
self.music_checkbox.setAutoFillBackground(False)
self.music_checkbox.setText("")
self.music_checkbox.setChecked(False)
self.music_checkbox.setObjectName("music_checkbox")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.music_checkbox)
self.music_text = QtWidgets.QLabel(self.centralwidget)
self.music_text.setLayoutDirection(QtCore.Qt.RightToLeft)
self.music_text.setObjectName("music_text")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.music_text)
self.music_play_text = QtWidgets.QLabel(self.centralwidget)
self.music_play_text.setLayoutDirection(QtCore.Qt.RightToLeft)
self.music_play_text.setObjectName("music_play_text")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.music_play_text)
self.verticalLayout.addLayout(self.formLayout)
self.line = QtWidgets.QFrame(self.centralwidget)
self.line.setFrameShape(QtWidgets.QFrame.HLine)
self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
self.line.setObjectName("line")
self.verticalLayout.addWidget(self.line)
self.formLayout_2 = QtWidgets.QFormLayout()
self.formLayout_2.setLabelAlignment(QtCore.Qt.AlignCenter)
self.formLayout_2.setFormAlignment(QtCore.Qt.AlignCenter)
self.formLayout_2.setObjectName("formLayout_2")
self.check_button = QtWidgets.QPushButton(self.centralwidget)
self.check_button.setEnabled(True)
self.check_button.setObjectName("check_button")
self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.check_button)
self.start_button = QtWidgets.QPushButton(self.centralwidget)
self.start_button.setEnabled(True)
self.start_button.setFlat(False)
self.start_button.setObjectName("start_button")
self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.start_button)
self.pause_button = QtWidgets.QPushButton(self.centralwidget)
self.pause_button.setObjectName("pause_button")
self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.pause_button)
self.verticalLayout.addLayout(self.formLayout_2)
self.line_5 = QtWidgets.QFrame(self.centralwidget)
self.line_5.setFrameShape(QtWidgets.QFrame.HLine)
self.line_5.setFrameShadow(QtWidgets.QFrame.Sunken)
self.line_5.setObjectName("line_5")
self.verticalLayout.addWidget(self.line_5)
self.formLayout_5 = QtWidgets.QFormLayout()
self.formLayout_5.setLabelAlignment(QtCore.Qt.AlignCenter)
self.formLayout_5.setFormAlignment(QtCore.Qt.AlignCenter)
self.formLayout_5.setContentsMargins(-1, 0, -1, -1)
self.formLayout_5.setObjectName("formLayout_5")
self.land_selected_button = QtWidgets.QPushButton(self.centralwidget)
self.land_selected_button.setObjectName("land_selected_button")
self.formLayout_5.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.land_selected_button)
self.land_all_button = QtWidgets.QPushButton(self.centralwidget)
self.land_all_button.setObjectName("land_all_button")
self.formLayout_5.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.land_all_button)
self.verticalLayout.addLayout(self.formLayout_5)
self.line_6 = QtWidgets.QFrame(self.centralwidget)
self.line_6.setFrameShape(QtWidgets.QFrame.HLine)
self.line_6.setFrameShadow(QtWidgets.QFrame.Sunken)
self.line_6.setObjectName("line_6")
self.verticalLayout.addWidget(self.line_6)
self.formLayout_7 = QtWidgets.QFormLayout()
self.formLayout_7.setFormAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
self.formLayout_7.setContentsMargins(-1, 0, -1, -1)
self.formLayout_7.setObjectName("formLayout_7")
self.visual_land_button = QtWidgets.QPushButton(self.centralwidget)
self.visual_land_button.setObjectName("visual_land_button")
self.formLayout_7.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.visual_land_button)
self.emergency_land_button = QtWidgets.QPushButton(self.centralwidget)
self.emergency_land_button.setObjectName("emergency_land_button")
self.formLayout_7.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.emergency_land_button)
self.verticalLayout.addLayout(self.formLayout_7)
self.line_2 = QtWidgets.QFrame(self.centralwidget)
self.line_2.setFrameShape(QtWidgets.QFrame.HLine)
self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
self.line_2.setObjectName("line_2")
self.verticalLayout.addWidget(self.line_2)
self.formLayout_3 = QtWidgets.QFormLayout()
self.formLayout_3.setLabelAlignment(QtCore.Qt.AlignCenter)
self.formLayout_3.setFormAlignment(QtCore.Qt.AlignCenter)
self.formLayout_3.setObjectName("formLayout_3")
self.disarm_all_button = QtWidgets.QPushButton(self.centralwidget)
self.disarm_all_button.setObjectName("disarm_all_button")
self.formLayout_3.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.disarm_all_button)
self.disarm_selected_button = QtWidgets.QPushButton(self.centralwidget)
self.disarm_selected_button.setObjectName("disarm_selected_button")
self.formLayout_3.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.disarm_selected_button)
self.verticalLayout.addLayout(self.formLayout_3)
self.line_3 = QtWidgets.QFrame(self.centralwidget)
self.line_3.setFrameShape(QtWidgets.QFrame.HLine)
self.line_3.setFrameShadow(QtWidgets.QFrame.Sunken)
self.line_3.setObjectName("line_3")
self.verticalLayout.addWidget(self.line_3)
self.formLayout_4 = QtWidgets.QFormLayout()
self.formLayout_4.setLabelAlignment(QtCore.Qt.AlignCenter)
self.formLayout_4.setFormAlignment(QtCore.Qt.AlignCenter)
self.formLayout_4.setObjectName("formLayout_4")
self.leds_button = QtWidgets.QPushButton(self.centralwidget)
self.leds_button.setObjectName("leds_button")
self.formLayout_4.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.leds_button)
self.takeoff_button = QtWidgets.QPushButton(self.centralwidget)
self.takeoff_button.setEnabled(True)
self.takeoff_button.setObjectName("takeoff_button")
self.formLayout_4.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.takeoff_button)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.z_checkbox = QtWidgets.QCheckBox(self.centralwidget)
self.z_checkbox.setLayoutDirection(QtCore.Qt.LeftToRight)
self.z_checkbox.setObjectName("z_checkbox")
self.horizontalLayout_2.addWidget(self.z_checkbox)
self.z_spin = QtWidgets.QDoubleSpinBox(self.centralwidget)
self.z_spin.setLayoutDirection(QtCore.Qt.LeftToRight)
self.z_spin.setDecimals(1)
self.z_spin.setProperty("value", 1.0)
self.z_spin.setObjectName("z_spin")
self.horizontalLayout_2.addWidget(self.z_spin)
self.formLayout_4.setLayout(2, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_2)
self.flip_button = QtWidgets.QPushButton(self.centralwidget)
self.flip_button.setObjectName("flip_button")
self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.flip_button)
self.verticalLayout.addLayout(self.formLayout_4)
self.line_4 = QtWidgets.QFrame(self.centralwidget)
self.line_4.setFrameShape(QtWidgets.QFrame.HLine)
self.line_4.setFrameShadow(QtWidgets.QFrame.Sunken)
self.line_4.setObjectName("line_4")
self.verticalLayout.addWidget(self.line_4)
self.formLayout_6 = QtWidgets.QFormLayout()
self.formLayout_6.setLabelAlignment(QtCore.Qt.AlignCenter)
self.formLayout_6.setFormAlignment(QtCore.Qt.AlignBottom|QtCore.Qt.AlignHCenter)
self.formLayout_6.setObjectName("formLayout_6")
self.reboot_fcu = QtWidgets.QPushButton(self.centralwidget)
self.reboot_fcu.setObjectName("reboot_fcu")
self.formLayout_6.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.reboot_fcu)
self.calibrate_gyro = QtWidgets.QPushButton(self.centralwidget)
self.calibrate_gyro.setObjectName("calibrate_gyro")
self.formLayout_6.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.calibrate_gyro)
self.calibrate_level = QtWidgets.QPushButton(self.centralwidget)
self.calibrate_level.setObjectName("calibrate_level")
self.formLayout_6.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.calibrate_level)
self.verticalLayout.addLayout(self.formLayout_6)
self.horizontalLayout.addLayout(self.verticalLayout)
self.horizontalLayout.setStretch(0, 1)
self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1360, 22))
self.menubar.setObjectName("menubar")
self.menuOptions = QtWidgets.QMenu(self.menubar)
self.menuOptions.setObjectName("menuOptions")
self.menuMusic_2 = QtWidgets.QMenu(self.menuOptions)
self.menuMusic_2.setObjectName("menuMusic_2")
self.menuTable = QtWidgets.QMenu(self.menubar)
self.menuTable.setObjectName("menuTable")
self.menuDrone_2 = QtWidgets.QMenu(self.menubar)
self.menuDrone_2.setObjectName("menuDrone_2")
self.menuSend = QtWidgets.QMenu(self.menuDrone_2)
self.menuSend.setObjectName("menuSend")
self.menuRestart = QtWidgets.QMenu(self.menuDrone_2)
self.menuRestart.setObjectName("menuRestart")
self.menuDeveloper_mode = QtWidgets.QMenu(self.menuDrone_2)
self.menuDeveloper_mode.setObjectName("menuDeveloper_mode")
MainWindow.setMenuBar(self.menubar)
self.action_send_animations = QtWidgets.QAction(MainWindow)
self.action_send_animations.setObjectName("action_send_animations")
self.action_send_configurations = QtWidgets.QAction(MainWindow)
self.action_send_configurations.setObjectName("action_send_configurations")
self.action_send_aruco_map = QtWidgets.QAction(MainWindow)
self.action_send_aruco_map.setObjectName("action_send_aruco_map")
self.action_update_client_repo = QtWidgets.QAction(MainWindow)
self.action_update_client_repo.setObjectName("action_update_client_repo")
self.actionSend_launch_file_for_clever = QtWidgets.QAction(MainWindow)
self.actionSend_launch_file_for_clever.setObjectName("actionSend_launch_file_for_clever")
self.action_send_launch_file = QtWidgets.QAction(MainWindow)
self.action_send_launch_file.setObjectName("action_send_launch_file")
self.action_restart_clever = QtWidgets.QAction(MainWindow)
self.action_restart_clever.setObjectName("action_restart_clever")
self.action_restart_clever_show = QtWidgets.QAction(MainWindow)
self.action_restart_clever_show.setObjectName("action_restart_clever_show")
self.action_select_all_rows = QtWidgets.QAction(MainWindow)
self.action_select_all_rows.setObjectName("action_select_all_rows")
self.action_set_start_to_current_position = QtWidgets.QAction(MainWindow)
self.action_set_start_to_current_position.setObjectName("action_set_start_to_current_position")
self.action_reset_start = QtWidgets.QAction(MainWindow)
self.action_reset_start.setObjectName("action_reset_start")
self.action_set_z_offset_to_ground = QtWidgets.QAction(MainWindow)
self.action_set_z_offset_to_ground.setObjectName("action_set_z_offset_to_ground")
self.action_reset_z_offset = QtWidgets.QAction(MainWindow)
self.action_reset_z_offset.setObjectName("action_reset_z_offset")
self.action_select_music_file = QtWidgets.QAction(MainWindow)
self.action_select_music_file.setObjectName("action_select_music_file")
self.action_play_music = QtWidgets.QAction(MainWindow)
self.action_play_music.setObjectName("action_play_music")
self.action_test_music_after = QtWidgets.QAction(MainWindow)
self.action_test_music_after.setObjectName("action_test_music_after")
self.actionFill = QtWidgets.QAction(MainWindow)
self.actionFill.setObjectName("actionFill")
self.action_send_any_file = QtWidgets.QAction(MainWindow)
self.action_send_any_file.setObjectName("action_send_any_file")
self.action_send_any_command = QtWidgets.QAction(MainWindow)
self.action_send_any_command.setObjectName("action_send_any_command")
self.action_stop_music = QtWidgets.QAction(MainWindow)
self.action_stop_music.setObjectName("action_stop_music")
self.action_remove_row = QtWidgets.QAction(MainWindow)
self.action_remove_row.setObjectName("action_remove_row")
self.action_send_calibrations = QtWidgets.QAction(MainWindow)
self.action_send_calibrations.setObjectName("action_send_calibrations")
self.action_reboot_all = QtWidgets.QAction(MainWindow)
self.action_reboot_all.setObjectName("action_reboot_all")
self.action_restart_chrony = QtWidgets.QAction(MainWindow)
self.action_restart_chrony.setObjectName("action_restart_chrony")
self.action_send_fcu_parameters = QtWidgets.QAction(MainWindow)
self.action_send_fcu_parameters.setObjectName("action_send_fcu_parameters")
self.action_toggle_select = QtWidgets.QAction(MainWindow)
self.action_toggle_select.setObjectName("action_toggle_select")
self.action_select_all = QtWidgets.QAction(MainWindow)
self.action_select_all.setObjectName("action_select_all")
self.action_deselect_all = QtWidgets.QAction(MainWindow)
self.action_deselect_all.setObjectName("action_deselect_all")
self.action_edit_server_config = QtWidgets.QAction(MainWindow)
self.action_edit_server_config.setObjectName("action_edit_server_config")
self.action_edit_any_config = QtWidgets.QAction(MainWindow)
self.action_edit_any_config.setObjectName("action_edit_any_config")
self.action_update_server_git = QtWidgets.QAction(MainWindow)
self.action_update_server_git.setEnabled(False)
self.action_update_server_git.setVisible(False)
self.action_update_server_git.setObjectName("action_update_server_git")
self.action_retrive_any_file = QtWidgets.QAction(MainWindow)
self.action_retrive_any_file.setObjectName("action_retrive_any_file")
self.action_restart_server = QtWidgets.QAction(MainWindow)
self.action_restart_server.setObjectName("action_restart_server")
self.action_configure_columns = QtWidgets.QAction(MainWindow)
self.action_configure_columns.setObjectName("action_configure_columns")
self.actionSomething = QtWidgets.QAction(MainWindow)
self.actionSomething.setObjectName("actionSomething")
self.action_send_animation = QtWidgets.QAction(MainWindow)
self.action_send_animation.setObjectName("action_send_animation")
self.menuMusic_2.addAction(self.action_select_music_file)
self.menuMusic_2.addAction(self.action_play_music)
self.menuMusic_2.addAction(self.action_stop_music)
self.menuOptions.addAction(self.menuMusic_2.menuAction())
self.menuOptions.addSeparator()
self.menuOptions.addAction(self.action_edit_server_config)
self.menuOptions.addAction(self.action_edit_any_config)
self.menuOptions.addSeparator()
self.menuOptions.addAction(self.action_update_server_git)
self.menuOptions.addAction(self.action_restart_server)
self.menuTable.addAction(self.action_toggle_select)
self.menuTable.addAction(self.action_select_all)
self.menuTable.addAction(self.action_deselect_all)
self.menuTable.addSeparator()
self.menuTable.addAction(self.action_remove_row)
self.menuTable.addSeparator()
self.menuTable.addAction(self.action_configure_columns)
self.menuSend.addAction(self.action_send_animations)
self.menuSend.addAction(self.action_send_calibrations)
self.menuSend.addSeparator()
self.menuSend.addAction(self.action_send_aruco_map)
self.menuSend.addAction(self.action_send_animation)
self.menuSend.addAction(self.action_send_configurations)
self.menuSend.addAction(self.action_send_launch_file)
self.menuSend.addAction(self.action_send_fcu_parameters)
self.menuSend.addSeparator()
self.menuSend.addAction(self.action_send_any_file)
self.menuSend.addAction(self.action_send_any_command)
self.menuRestart.addAction(self.action_restart_chrony)
self.menuRestart.addAction(self.action_restart_clever)
self.menuRestart.addAction(self.action_restart_clever_show)
self.menuRestart.addSeparator()
self.menuDeveloper_mode.addAction(self.action_update_client_repo)
self.menuDrone_2.addAction(self.menuSend.menuAction())
self.menuDrone_2.addAction(self.action_retrive_any_file)
self.menuDrone_2.addAction(self.menuRestart.menuAction())
self.menuDrone_2.addSeparator()
self.menuDrone_2.addAction(self.action_set_start_to_current_position)
self.menuDrone_2.addAction(self.action_reset_start)
self.menuDrone_2.addAction(self.action_set_z_offset_to_ground)
self.menuDrone_2.addAction(self.action_reset_z_offset)
self.menuDrone_2.addSeparator()
self.menuDrone_2.addAction(self.menuDeveloper_mode.menuAction())
self.menuDrone_2.addSeparator()
self.menuDrone_2.addAction(self.action_reboot_all)
self.menubar.addAction(self.menuDrone_2.menuAction())
self.menubar.addAction(self.menuOptions.menuAction())
self.menubar.addAction(self.menuTable.menuAction())
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
MainWindow.setTabOrder(self.start_delay_spin, self.tableView)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Clever Drone Show"))
self.start_text.setText(_translate("MainWindow", " Start after"))
self.start_delay_spin.setSuffix(_translate("MainWindow", " s"))
self.music_delay_spin.setSuffix(_translate("MainWindow", " s"))
self.music_text.setText(_translate("MainWindow", " Music after"))
self.music_play_text.setText(_translate("MainWindow", " Play music"))
self.check_button.setText(_translate("MainWindow", "Preflight check"))
self.start_button.setText(_translate("MainWindow", "Start animation"))
self.pause_button.setText(_translate("MainWindow", "Pause"))
self.land_selected_button.setText(_translate("MainWindow", "Land selected"))
self.land_all_button.setText(_translate("MainWindow", "Land ALL"))
self.visual_land_button.setText(_translate("MainWindow", "Visual land"))
self.emergency_land_button.setText(_translate("MainWindow", "Emergency land"))
self.disarm_all_button.setText(_translate("MainWindow", "Disarm ALL"))
self.disarm_selected_button.setText(_translate("MainWindow", "Disarm selected"))
self.leds_button.setText(_translate("MainWindow", "Test leds"))
self.takeoff_button.setText(_translate("MainWindow", "Takeoff"))
self.z_checkbox.setText(_translate("MainWindow", " Z ="))
self.z_spin.setSuffix(_translate("MainWindow", " m"))
self.flip_button.setText(_translate("MainWindow", "Flip"))
self.reboot_fcu.setText(_translate("MainWindow", "Reboot FCU"))
self.calibrate_gyro.setText(_translate("MainWindow", "Calibrate gyro"))
self.calibrate_level.setText(_translate("MainWindow", "Calibrate level"))
self.menuOptions.setTitle(_translate("MainWindow", "server"))
self.menuMusic_2.setTitle(_translate("MainWindow", "Music"))
self.menuTable.setTitle(_translate("MainWindow", "Table"))
self.menuDrone_2.setTitle(_translate("MainWindow", "Selected drones"))
self.menuSend.setTitle(_translate("MainWindow", "Send"))
self.menuRestart.setTitle(_translate("MainWindow", "Restart service"))
self.menuDeveloper_mode.setTitle(_translate("MainWindow", "Developer mode"))
self.action_send_animations.setText(_translate("MainWindow", "Animations"))
self.action_send_configurations.setText(_translate("MainWindow", "Configuration"))
self.action_send_aruco_map.setText(_translate("MainWindow", "Aruco map"))
self.action_update_client_repo.setText(_translate("MainWindow", "Update clever-show git"))
self.actionSend_launch_file_for_clever.setText(_translate("MainWindow", "Send launch file for clever"))
self.action_send_launch_file.setText(_translate("MainWindow", "Launch files folder"))
self.action_restart_clever.setText(_translate("MainWindow", "clever"))
self.action_restart_clever_show.setText(_translate("MainWindow", "clever-show"))
self.action_select_all_rows.setText(_translate("MainWindow", "Select all drones"))
self.action_select_all_rows.setShortcut(_translate("MainWindow", "Ctrl+A"))
self.action_set_start_to_current_position.setText(_translate("MainWindow", "Set start X Y to current position"))
self.action_reset_start.setText(_translate("MainWindow", "Reset start position"))
self.action_set_z_offset_to_ground.setText(_translate("MainWindow", "Set Z offset to ground"))
self.action_reset_z_offset.setText(_translate("MainWindow", "Reset Z offset"))
self.action_select_music_file.setText(_translate("MainWindow", "Select file"))
self.action_play_music.setText(_translate("MainWindow", "Play"))
self.action_test_music_after.setText(_translate("MainWindow", "Test music after"))
self.actionFill.setText(_translate("MainWindow", "fill"))
self.action_send_any_file.setText(_translate("MainWindow", "File"))
self.action_send_any_command.setText(_translate("MainWindow", "Command"))
self.action_stop_music.setText(_translate("MainWindow", "Stop"))
self.action_remove_row.setText(_translate("MainWindow", "Remove selected drones"))
self.action_remove_row.setShortcut(_translate("MainWindow", "Ctrl+Del"))
self.action_send_calibrations.setText(_translate("MainWindow", "Camera calibrations"))
self.action_reboot_all.setText(_translate("MainWindow", "Reboot"))
self.action_restart_chrony.setText(_translate("MainWindow", "chrony"))
self.action_send_fcu_parameters.setText(_translate("MainWindow", "FCU parameters file"))
self.action_toggle_select.setText(_translate("MainWindow", "Toggle select"))
self.action_toggle_select.setShortcut(_translate("MainWindow", "Ctrl+A"))
self.action_select_all.setText(_translate("MainWindow", "Select all"))
self.action_select_all.setShortcut(_translate("MainWindow", "Shift+A"))
self.action_deselect_all.setText(_translate("MainWindow", "Deselect all"))
self.action_deselect_all.setShortcut(_translate("MainWindow", "Alt+A"))
self.action_edit_server_config.setText(_translate("MainWindow", "Edit server config"))
self.action_edit_any_config.setText(_translate("MainWindow", "Edit any config"))
self.action_update_server_git.setText(_translate("MainWindow", "Update server git"))
self.action_retrive_any_file.setText(_translate("MainWindow", "Retrive file"))
self.action_restart_server.setText(_translate("MainWindow", "Restart server"))
self.action_configure_columns.setText(_translate("MainWindow", "Configure columns"))
self.actionSomething.setText(_translate("MainWindow", "something"))
self.action_send_animation.setText(_translate("MainWindow", "Animation"))

724
server/server_gui.ui Normal file
View File

@@ -0,0 +1,724 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1360</width>
<height>869</height>
</rect>
</property>
<property name="windowTitle">
<string>Clever Drone Show</string>
</property>
<widget class="QWidget" name="centralwidget">
<property name="enabled">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
<property name="sizeConstraint">
<enum>QLayout::SetMaximumSize</enum>
</property>
<item>
<widget class="QTableView" name="tableView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectItems</enum>
</property>
<property name="textElideMode">
<enum>Qt::ElideMiddle</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>50</number>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>50</number>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetMaximumSize</enum>
</property>
<item>
<layout class="QFormLayout" name="formLayout">
<property name="labelAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="formAlignment">
<set>Qt::AlignHCenter|Qt::AlignTop</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="start_text">
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string> Start after</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="start_delay_spin">
<property name="suffix">
<string> s</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="music_delay_spin">
<property name="suffix">
<string> s</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="maximum">
<double>1000.000000000000000</double>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="music_checkbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="contextMenuPolicy">
<enum>Qt::DefaultContextMenu</enum>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="text">
<string/>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="music_text">
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string> Music after</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="music_play_text">
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string> Play music</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_2">
<property name="labelAlignment">
<set>Qt::AlignCenter</set>
</property>
<property name="formAlignment">
<set>Qt::AlignCenter</set>
</property>
<item row="0" column="1">
<widget class="QPushButton" name="check_button">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Preflight check</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="start_button">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Start animation</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="pause_button">
<property name="text">
<string>Pause</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_5">
<property name="labelAlignment">
<set>Qt::AlignCenter</set>
</property>
<property name="formAlignment">
<set>Qt::AlignCenter</set>
</property>
<property name="topMargin">
<number>0</number>
</property>
<item row="0" column="1">
<widget class="QPushButton" name="land_selected_button">
<property name="text">
<string>Land selected</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="land_all_button">
<property name="text">
<string>Land ALL</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_7">
<property name="formAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="topMargin">
<number>0</number>
</property>
<item row="1" column="1">
<widget class="QPushButton" name="visual_land_button">
<property name="text">
<string>Visual land</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="emergency_land_button">
<property name="text">
<string>Emergency land</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_3">
<property name="labelAlignment">
<set>Qt::AlignCenter</set>
</property>
<property name="formAlignment">
<set>Qt::AlignCenter</set>
</property>
<item row="0" column="1">
<widget class="QPushButton" name="disarm_all_button">
<property name="text">
<string>Disarm ALL</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="disarm_selected_button">
<property name="text">
<string>Disarm selected</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_4">
<property name="labelAlignment">
<set>Qt::AlignCenter</set>
</property>
<property name="formAlignment">
<set>Qt::AlignCenter</set>
</property>
<item row="0" column="1">
<widget class="QPushButton" name="leds_button">
<property name="text">
<string>Test leds</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="takeoff_button">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Takeoff</string>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QCheckBox" name="z_checkbox">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string> Z =</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="z_spin">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="suffix">
<string> m</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="flip_button">
<property name="text">
<string>Flip</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_6">
<property name="labelAlignment">
<set>Qt::AlignCenter</set>
</property>
<property name="formAlignment">
<set>Qt::AlignBottom|Qt::AlignHCenter</set>
</property>
<item row="0" column="1">
<widget class="QPushButton" name="reboot_fcu">
<property name="text">
<string>Reboot FCU</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="calibrate_gyro">
<property name="text">
<string>Calibrate gyro</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="calibrate_level">
<property name="text">
<string>Calibrate level</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1360</width>
<height>22</height>
</rect>
</property>
<widget class="QMenu" name="menuOptions">
<property name="title">
<string>Server</string>
</property>
<widget class="QMenu" name="menuMusic_2">
<property name="title">
<string>Music</string>
</property>
<addaction name="action_select_music_file"/>
<addaction name="action_play_music"/>
<addaction name="action_stop_music"/>
</widget>
<addaction name="menuMusic_2"/>
<addaction name="separator"/>
<addaction name="action_edit_server_config"/>
<addaction name="action_edit_any_config"/>
<addaction name="separator"/>
<addaction name="action_update_server_git"/>
<addaction name="action_restart_server"/>
</widget>
<widget class="QMenu" name="menuTable">
<property name="title">
<string>Table</string>
</property>
<addaction name="action_toggle_select"/>
<addaction name="action_select_all"/>
<addaction name="action_deselect_all"/>
<addaction name="separator"/>
<addaction name="action_remove_row"/>
<addaction name="separator"/>
<addaction name="action_configure_columns"/>
</widget>
<widget class="QMenu" name="menuDrone_2">
<property name="title">
<string>Selected drones</string>
</property>
<widget class="QMenu" name="menuSend">
<property name="title">
<string>Send</string>
</property>
<addaction name="action_send_animations"/>
<addaction name="action_send_calibrations"/>
<addaction name="separator"/>
<addaction name="action_send_aruco_map"/>
<addaction name="action_send_animation"/>
<addaction name="action_send_configurations"/>
<addaction name="action_send_launch_file"/>
<addaction name="action_send_fcu_parameters"/>
<addaction name="separator"/>
<addaction name="action_send_any_file"/>
<addaction name="action_send_any_command"/>
</widget>
<widget class="QMenu" name="menuRestart">
<property name="title">
<string>Restart service</string>
</property>
<addaction name="action_restart_chrony"/>
<addaction name="action_restart_clever"/>
<addaction name="action_restart_clever_show"/>
<addaction name="separator"/>
</widget>
<widget class="QMenu" name="menuDeveloper_mode">
<property name="title">
<string>Developer mode</string>
</property>
<addaction name="action_update_client_repo"/>
</widget>
<addaction name="menuSend"/>
<addaction name="action_retrive_any_file"/>
<addaction name="menuRestart"/>
<addaction name="separator"/>
<addaction name="action_set_start_to_current_position"/>
<addaction name="action_reset_start"/>
<addaction name="action_set_z_offset_to_ground"/>
<addaction name="action_reset_z_offset"/>
<addaction name="separator"/>
<addaction name="menuDeveloper_mode"/>
<addaction name="separator"/>
<addaction name="action_reboot_all"/>
</widget>
<addaction name="menuDrone_2"/>
<addaction name="menuOptions"/>
<addaction name="menuTable"/>
</widget>
<action name="action_send_animations">
<property name="text">
<string>Animations</string>
</property>
</action>
<action name="action_send_configurations">
<property name="text">
<string>Configuration</string>
</property>
</action>
<action name="action_send_aruco_map">
<property name="text">
<string>Aruco map</string>
</property>
</action>
<action name="action_update_client_repo">
<property name="text">
<string>Update clever-show git</string>
</property>
</action>
<action name="actionSend_launch_file_for_clever">
<property name="text">
<string>Send launch file for clever</string>
</property>
</action>
<action name="action_send_launch_file">
<property name="text">
<string>Launch files folder</string>
</property>
</action>
<action name="action_restart_clever">
<property name="text">
<string>clever</string>
</property>
</action>
<action name="action_restart_clever_show">
<property name="text">
<string>clever-show</string>
</property>
</action>
<action name="action_select_all_rows">
<property name="text">
<string>Select all drones</string>
</property>
<property name="shortcut">
<string>Ctrl+A</string>
</property>
</action>
<action name="action_set_start_to_current_position">
<property name="text">
<string>Set start X Y to current position</string>
</property>
</action>
<action name="action_reset_start">
<property name="text">
<string>Reset start position</string>
</property>
</action>
<action name="action_set_z_offset_to_ground">
<property name="text">
<string>Set Z offset to ground</string>
</property>
</action>
<action name="action_reset_z_offset">
<property name="text">
<string>Reset Z offset</string>
</property>
</action>
<action name="action_select_music_file">
<property name="text">
<string>Select file</string>
</property>
</action>
<action name="action_play_music">
<property name="text">
<string>Play</string>
</property>
</action>
<action name="action_test_music_after">
<property name="text">
<string>Test music after</string>
</property>
</action>
<action name="actionFill">
<property name="text">
<string>fill</string>
</property>
</action>
<action name="action_send_any_file">
<property name="text">
<string>File</string>
</property>
</action>
<action name="action_send_any_command">
<property name="text">
<string>Command</string>
</property>
</action>
<action name="action_stop_music">
<property name="text">
<string>Stop</string>
</property>
</action>
<action name="action_remove_row">
<property name="text">
<string>Remove selected drones</string>
</property>
<property name="shortcut">
<string>Ctrl+Del</string>
</property>
</action>
<action name="action_send_calibrations">
<property name="text">
<string>Camera calibrations</string>
</property>
</action>
<action name="action_reboot_all">
<property name="text">
<string>Reboot</string>
</property>
</action>
<action name="action_restart_chrony">
<property name="text">
<string>chrony</string>
</property>
</action>
<action name="action_send_fcu_parameters">
<property name="text">
<string>FCU parameters file</string>
</property>
</action>
<action name="action_toggle_select">
<property name="text">
<string>Toggle select</string>
</property>
<property name="shortcut">
<string>Ctrl+A</string>
</property>
</action>
<action name="action_select_all">
<property name="text">
<string>Select all</string>
</property>
<property name="shortcut">
<string>Shift+A</string>
</property>
</action>
<action name="action_deselect_all">
<property name="text">
<string>Deselect all</string>
</property>
<property name="shortcut">
<string>Alt+A</string>
</property>
</action>
<action name="action_edit_server_config">
<property name="text">
<string>Edit server config</string>
</property>
</action>
<action name="action_edit_any_config">
<property name="text">
<string>Edit any config</string>
</property>
</action>
<action name="action_update_server_git">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Update server git</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</action>
<action name="action_retrive_any_file">
<property name="text">
<string>Retrive file</string>
</property>
</action>
<action name="action_restart_server">
<property name="text">
<string>Restart server</string>
</property>
</action>
<action name="action_configure_columns">
<property name="text">
<string>Configure columns</string>
</property>
</action>
<action name="actionSomething">
<property name="text">
<string>something</string>
</property>
</action>
<action name="action_send_animation">
<property name="text">
<string>Animation</string>
</property>
</action>
</widget>
<tabstops>
<tabstop>start_delay_spin</tabstop>
<tabstop>tableView</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

73
server/visual_land.py Normal file
View File

@@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'visual_land.ui'
#
# Created by: PyQt5 UI code generator 5.13.0
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(746, 620)
Dialog.setStyleSheet("")
self.two_button = QtWidgets.QPushButton(Dialog)
self.two_button.setGeometry(QtCore.QRect(420, 120, 231, 171))
self.two_button.setSizeIncrement(QtCore.QSize(16, 16))
self.two_button.setStyleSheet("QPushButton{\n"
"color: white;\n"
"font-weight: 600;\n"
"font-size: 25pt;\n"
"background-color: red;\n"
"}")
self.two_button.setObjectName("two_button")
self.label = QtWidgets.QLabel(Dialog)
self.label.setGeometry(QtCore.QRect(60, 30, 631, 51))
font = QtGui.QFont()
font.setPointSize(20)
self.label.setFont(font)
self.label.setLayoutDirection(QtCore.Qt.LeftToRight)
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.label.setObjectName("label")
self.one_button = QtWidgets.QPushButton(Dialog)
self.one_button.setGeometry(QtCore.QRect(90, 120, 231, 171))
self.one_button.setSizeIncrement(QtCore.QSize(16, 16))
self.one_button.setStyleSheet("QPushButton{\n"
"color: white;\n"
"font-weight: 600;\n"
"font-size: 25pt;\n"
"background-color: green;\n"
"}")
self.one_button.setObjectName("one_button")
self.land_emergency_button = QtWidgets.QPushButton(Dialog)
self.land_emergency_button.setGeometry(QtCore.QRect(90, 340, 561, 81))
self.land_emergency_button.setStyleSheet("QPushButton{\n"
"font-weight: 600;\n"
"font-size: 25pt;\n"
"background-color: white;\n"
"}")
self.land_emergency_button.setObjectName("land_emergency_button")
self.disarm_emergency_button = QtWidgets.QPushButton(Dialog)
self.disarm_emergency_button.setGeometry(QtCore.QRect(90, 460, 561, 81))
self.disarm_emergency_button.setStyleSheet("QPushButton{\n"
"font-weight: 600;\n"
"font-size: 25pt;\n"
"background-color: white;\n"
"}")
self.disarm_emergency_button.setObjectName("disarm_emergency_button")
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Visual land"))
self.two_button.setText(_translate("Dialog", "2"))
self.label.setText(_translate("Dialog", "Select the group with the defective copter"))
self.one_button.setText(_translate("Dialog", "1"))
self.land_emergency_button.setText(_translate("Dialog", "Land"))
self.disarm_emergency_button.setText(_translate("Dialog", "Disarm"))

140
server/visual_land.ui Normal file
View File

@@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>746</width>
<height>620</height>
</rect>
</property>
<property name="windowTitle">
<string>Visual land</string>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<widget class="QPushButton" name="two_button">
<property name="geometry">
<rect>
<x>420</x>
<y>120</y>
<width>231</width>
<height>171</height>
</rect>
</property>
<property name="sizeIncrement">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">QPushButton{
color: white;
font-weight: 600;
font-size: 25pt;
background-color: red;
}</string>
</property>
<property name="text">
<string>2</string>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>60</x>
<y>30</y>
<width>631</width>
<height>51</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>20</pointsize>
</font>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Select the group with the defective copter</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
<widget class="QPushButton" name="one_button">
<property name="geometry">
<rect>
<x>90</x>
<y>120</y>
<width>231</width>
<height>171</height>
</rect>
</property>
<property name="sizeIncrement">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">QPushButton{
color: white;
font-weight: 600;
font-size: 25pt;
background-color: green;
}</string>
</property>
<property name="text">
<string>1</string>
</property>
</widget>
<widget class="QPushButton" name="land_emergency_button">
<property name="geometry">
<rect>
<x>90</x>
<y>340</y>
<width>561</width>
<height>81</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">QPushButton{
font-weight: 600;
font-size: 25pt;
background-color: white;
}</string>
</property>
<property name="text">
<string>Land</string>
</property>
</widget>
<widget class="QPushButton" name="disarm_emergency_button">
<property name="geometry">
<rect>
<x>90</x>
<y>460</y>
<width>561</width>
<height>81</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">QPushButton{
font-weight: 600;
font-size: 25pt;
background-color: white;
}</string>
</property>
<property name="text">
<string>Disarm</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,114 @@
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QKeySequence
from PyQt5 import QtWidgets
import visual_land
import math
import logging
import sys
from functools import partial
from lib import b_partial
# TODO: previous step and reset
class VisualLandDialog(QtWidgets.QDialog):
def __init__(self, model):
super(VisualLandDialog, self).__init__()
self.ui = visual_land.Ui_Dialog()
self.setupUi()
self.model = model
self.row_min = 0
self.row_max = self.model.rowCount() - 1
self._finished = False
def setupUi(self):
self.ui.setupUi(self)
self.ui.one_button.clicked.connect(partial(self.selection_choice, 1))
self.ui.two_button.clicked.connect(partial(self.selection_choice, 2))
self.ui.land_emergency_button.clicked.connect(b_partial(self.send_to_selected, "land"))
self.ui.disarm_emergency_button.clicked.connect(b_partial(self.send_to_selected, "disarm"))
self.ui.one_button.setShortcut(QKeySequence("1"))
self.ui.two_button.setShortcut(QKeySequence("2"))
self.ui.land_emergency_button.setShortcut(QKeySequence("L"))
self.ui.disarm_emergency_button.setShortcut(QKeySequence("D"))
@property
def row_mid(self):
return int(math.ceil((self.row_min + self.row_max) / 2.0))
def send_to_row(self, row, message, args=(), kwargs=None):
logging.debug(f"Send {message}: {args}, {kwargs} to {row}")
self.model.data_contents[row].client.send_message(message, args=args, kwargs=kwargs)
# test[row] = args, kwargs # for testing
# print(test)
def clear_leds(self, rows):
for row in rows:
self.send_to_row(row, "led_fill")
def start(self):
self.show()
self.send_led_indication()
self.exec()
def send_led_indication(self):
for row in range(self.row_min, self.row_mid):
self.send_to_row(row, "led_fill", kwargs={"green": 255})
for row in range(self.row_mid, self.row_max + 1):
self.send_to_row(row, "led_fill", kwargs={"red": 255})
@pyqtSlot()
def selection_choice(self, choice):
if self.row_min == self.row_max:
# self.ui.one_button.setDisabled(True) # maybe?
# self.ui.two_button.setDisabled(True)
self.send_to_selected("land")
return
if choice == 1:
to_clear = range(self.row_mid, self.row_max + 1)
self.row_max = self.row_mid - 1
elif choice == 2:
to_clear = range(self.row_min, self.row_mid)
self.row_min = self.row_mid
else:
return
self.clear_leds(to_clear)
self.send_led_indication()
@pyqtSlot()
def send_to_selected(self, message, args=(), kwargs=None):
for row in range(self.row_min, self.row_max + 1):
self.send_to_row(row, message, args, kwargs)
self._finished = True
self.close()
def closeEvent(self, event):
if not self._finished:
self.clear_leds(range(self.row_min, self.row_max + 1))
event.accept()
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
app = QtWidgets.QApplication(sys.argv)
import copter_table_models
model = copter_table_models.CopterDataModel()
for i in range(10):
model.add_client()
dialog = VisualLandDialog(model)
test = list(range(10))
dialog.start()