mirror of
https://github.com/CopterExpress/clever-show.git
synced 2026-05-29 16:29:34 +00:00
Merge branch 'master' into qt-gui-update
This commit is contained in:
7
Drone/README.md
Normal file
7
Drone/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Client of clever-show
|
||||
|
||||
Application for remote synchronized control of drones and emergency drone protection module.
|
||||
|
||||
Documentation is located here:
|
||||
* English
|
||||
* [Russian](../docs/ru/client.md)
|
||||
@@ -115,7 +115,7 @@ class Client(object):
|
||||
logger.critical("Caught interrupt, exiting!")
|
||||
self.selector.close()
|
||||
|
||||
def _reconnect(self, timeout=3.0, attempt_limit=5):
|
||||
def _reconnect(self, timeout=2.0, attempt_limit=3):
|
||||
logger.info("Trying to connect to {}:{} ...".format(self.server_host, self.server_port))
|
||||
attempt_count = 0
|
||||
while not self.connected:
|
||||
@@ -153,7 +153,7 @@ class Client(object):
|
||||
self.selector.register(self.client_socket, events, data=self.server_connection)
|
||||
self.server_connection.connect(self.selector, self.client_socket, (self.server_host, self.server_port))
|
||||
|
||||
def broadcast_bind(self, timeout=3.0, attempt_limit=5):
|
||||
def broadcast_bind(self, timeout=2.0, attempt_limit=3):
|
||||
broadcast_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
broadcast_client.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
broadcast_client.bind(("", self.broadcast_port))
|
||||
|
||||
@@ -791,6 +791,7 @@ class Telemetry:
|
||||
try:
|
||||
self.calibration_status = get_calibration_status()
|
||||
self.system_status = get_sys_status()
|
||||
self.battery = self.get_battery(self.ros_telemetry)
|
||||
except rospy.ServiceException:
|
||||
rospy.logdebug("Some service is unavailable")
|
||||
self.selfcheck = ["WAIT_ROS"]
|
||||
@@ -798,7 +799,6 @@ class Telemetry:
|
||||
rospy.logdebug(e)
|
||||
except rospy.TransportException as e:
|
||||
rospy.logdebug(e)
|
||||
self.battery = self.get_battery(self.ros_telemetry)
|
||||
|
||||
def update(self):
|
||||
self.update_telemetry_fast()
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
[Русская версия](README_RU.md)
|
||||
|
||||
Software for making the drone show controlled by Raspberry Pi and COEX [Clever](https://github.com/CopterExpress/clever) package.
|
||||
Software for making the drone show controlled by Raspberry Pi, PX4 and COEX [Clever](https://github.com/CopterExpress/clever) package.
|
||||
|
||||
Create animation in Blender, convert it to drone paths, set up the drones and run your own show!
|
||||
|
||||
[](https://travis-ci.org/CopterExpress/clever-show)
|
||||
|
||||
@@ -14,9 +16,9 @@ Software for making the drone show controlled by Raspberry Pi and COEX [Clever](
|
||||
|
||||
## This software includes
|
||||
|
||||
* [Drone side](https://github.com/CopterExpress/clever-show/tree/master/Drone) with autonomous flight module, animation player module and client application for remote synchronized control of drones
|
||||
* [Drone side](https://github.com/CopterExpress/clever-show/tree/master/Drone) for remote synchronized control of drones with emergency drone protection module
|
||||
* [Server side](https://github.com/CopterExpress/clever-show/tree/master/Server) for making the drone show with ability of tuning drones, animation and music
|
||||
* [Blender 2.8 addon](https://github.com/CopterExpress/clever-show/tree/master/blender-addon) for animation export to drone paths
|
||||
* [Blender 2.8 addon](https://github.com/CopterExpress/clever-show/tree/master/blender-addon) for exporting animation to drone paths
|
||||
* [Raspberry Pi image](https://github.com/CopterExpress/clever-show/releases/latest) for quick launch software on the drones
|
||||
|
||||
## Documentation
|
||||
|
||||
17
README_RU.md
17
README_RU.md
@@ -1,19 +1,28 @@
|
||||
# clever-show
|
||||
|
||||
[English version](README.md)
|
||||
|
||||
Програмное обеспечение для запуска шоу дронов под управлением Raspberry Pi с пакетом COEX [Clever](https://github.com/CopterExpress/clever).
|
||||
Програмное обеспечение для запуска шоу дронов под управлением Raspberry Pi с пакетом COEX [Clever](https://github.com/CopterExpress/clever) и полётного контроллера с прошивкой PX4.
|
||||
|
||||
Создайте анимацию в Blender, сконвертируйте её в полётные пути дронов, настройте дроны и запустите своё собственное шоу дронов!
|
||||
|
||||
[](https://travis-ci.org/CopterExpress/clever-show)
|
||||
|
||||
## Демонстрационное видео
|
||||
|
||||
[](http://www.youtube.com/watch?v=HdHbZFz7nR0)
|
||||
|
||||
12 дронов выступают в Электротеатре Станиславский, Москва.
|
||||
|
||||
## Пакет включает в себя
|
||||
* [Набор ПО для дрона](https://github.com/CopterExpress/clever-show/tree/master/Drone) с библиотекой для автономного полёта, модулем воспроизведения анимаций и клиентским приложением для удаленного синхронизированного управления дронами
|
||||
|
||||
* [Набор ПО для дрона](https://github.com/CopterExpress/clever-show/tree/master/Drone) с клиентским приложением для удаленного синхронизированного управления дронами и модулем экстренной посадки.
|
||||
* [Серверное приложение](https://github.com/CopterExpress/clever-show/tree/master/Server) для создания шоу и настройки дронов, анимации и музыки
|
||||
* [Аддон для Blender 2.8](https://github.com/CopterExpress/clever-show/tree/master/blender-addon) для преобразования анимации полёта коптеров, созданной в Blender, в файлы полётов для каждого коптера
|
||||
* [Образ для Raspberry Pi](https://github.com/CopterExpress/clever-show/releases/latest) для быстрого запуска ПО на коптере
|
||||
|
||||
## Документация
|
||||
|
||||
Инструкция по запуску ПО находится [здесь](docs/ru/start-tutorial.md).
|
||||
|
||||
Подробная документация расположена в папке [docs](https://github.com/CopterExpress/clever-show/tree/master/docs).
|
||||
|
||||
|
||||
|
||||
7
Server/README.md
Normal file
7
Server/README.md
Normal 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)
|
||||
@@ -336,7 +336,8 @@ def view_battery(value):
|
||||
def view_selfcheck(value):
|
||||
if isinstance(value, list):
|
||||
if len(value)==1:
|
||||
return value[0]
|
||||
if len(value[0]) <= 8:
|
||||
return value[0]
|
||||
return "ERROR"
|
||||
return value
|
||||
|
||||
|
||||
@@ -205,10 +205,6 @@ class Server(messaging.Singleton):
|
||||
logging.error(f"Unexpected error {e}!")
|
||||
raise
|
||||
|
||||
finally:
|
||||
broadcast_sock.close()
|
||||
logging.info("Broadcast sender thread stopped, socked closed!")
|
||||
|
||||
def _broadcast_listen(self):
|
||||
logging.info("Broadcast listener thread started!")
|
||||
broadcast_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
|
||||
@@ -21,13 +21,14 @@ from quamash import QEventLoop
|
||||
from server_gui import Ui_MainWindow
|
||||
|
||||
from server import Server, Client, now
|
||||
|
||||
import messaging_lib as messaging
|
||||
import config as cfg
|
||||
|
||||
import copter_table_models as table
|
||||
from copter_table import CopterTableWidget
|
||||
#from emergency import *
|
||||
# TODO uncomment
|
||||
from visual_land_dialog import VisualLandDialog
|
||||
|
||||
|
||||
|
||||
def multi_glob(*patterns):
|
||||
@@ -272,7 +273,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.send_to_selected("resume", {"time": server.time_now() + time_gap})
|
||||
self.ui.pause_button.setText('Pause')
|
||||
|
||||
|
||||
@pyqtSlot()
|
||||
def land_selected(self):
|
||||
for copter in self.model.user_selected():
|
||||
@@ -286,7 +286,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
def disarm_all(self):
|
||||
Client.broadcast_message("disarm")
|
||||
|
||||
|
||||
@pyqtSlot()
|
||||
def test_leds_selected(self):
|
||||
for copter in self.model.user_selected():
|
||||
@@ -538,51 +537,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.player.play()
|
||||
|
||||
@pyqtSlot()
|
||||
def emergency(self): # TODO refactor for the sake of god
|
||||
client_row_min = 0
|
||||
client_row_max = self.model.rowCount() - 1
|
||||
result = -1
|
||||
while (result != 0) and (result != 3) and (result != 4):
|
||||
# light_green_red(min, max)
|
||||
client_row_mid = int(math.ceil((client_row_max + client_row_min) / 2.0))
|
||||
print(client_row_min, client_row_mid, client_row_max)
|
||||
for row_num in range(client_row_min, client_row_mid):
|
||||
self.model.data_contents[row_num].client \
|
||||
.send_message("led_fill", {"green": 255})
|
||||
for row_num in range(client_row_mid, client_row_max + 1):
|
||||
self.model.data_contents[row_num].client \
|
||||
.send_message("led_fill", {"red": 255})
|
||||
|
||||
Dialog = QtWidgets.QDialog()
|
||||
ui = Ui_Dialog()
|
||||
ui.setupUi(Dialog)
|
||||
Dialog.show()
|
||||
result = Dialog.exec()
|
||||
print("Dialog result: {}".format(result))
|
||||
|
||||
if client_row_max != client_row_min:
|
||||
if result == 1:
|
||||
for row_num in range(client_row_mid, client_row_max + 1):
|
||||
self.model.data_contents[row_num].client \
|
||||
.send_message("led_fill")
|
||||
client_row_max = client_row_mid - 1
|
||||
|
||||
elif result == 2:
|
||||
for row_num in range(client_row_min, client_row_mid):
|
||||
self.model.data_contents[row_num].client \
|
||||
.send_message("led_fill")
|
||||
client_row_min = client_row_mid
|
||||
|
||||
if result == 0:
|
||||
Client.broadcast_message("led_fill")
|
||||
elif result == 3:
|
||||
for row_num in range(client_row_min, client_row_max + 1):
|
||||
self.model.data_contents[row_num].client \
|
||||
.send_message("land")
|
||||
elif result == 4:
|
||||
for row_num in range(client_row_min, client_row_max + 1):
|
||||
self.model.data_contents[row_num].client \
|
||||
.send_message("disarm")
|
||||
def visual_land(self):
|
||||
dialog = VisualLandDialog(self.model)
|
||||
dialog.start()
|
||||
|
||||
|
||||
@messaging.message_callback("telemetry")
|
||||
|
||||
112
Server/visual_land_dialog.py
Normal file
112
Server/visual_land_dialog.py
Normal file
@@ -0,0 +1,112 @@
|
||||
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
|
||||
|
||||
|
||||
# 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(partial(self.send_to_selected, "land", None))
|
||||
self.ui.disarm_emergency_button.clicked.connect(partial(self.send_to_selected, "disarm", None))
|
||||
|
||||
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=None):
|
||||
logging.debug(f"Send {message}: {args} to {row}")
|
||||
self.model.data_contents[row].client.send_message(message, args)
|
||||
# test[row] = args # 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", {"green": 255})
|
||||
|
||||
for row in range(self.row_mid, self.row_max + 1):
|
||||
self.send_to_row(row, "led_fill", {"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)
|
||||
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=None):
|
||||
for row in range(self.row_min, self.row_max + 1):
|
||||
self.send_to_row(row, message, args)
|
||||
|
||||
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(copter_table_models.StatedCopterData())
|
||||
|
||||
dialog = VisualLandDialog(model)
|
||||
test = list(range(10))
|
||||
|
||||
dialog.start()
|
||||
BIN
blender-addon/examples/two_drones_test.blend
Normal file
BIN
blender-addon/examples/two_drones_test.blend
Normal file
Binary file not shown.
@@ -21,8 +21,8 @@ cd source-dir
|
||||
sudo docker run --privileged -it --rm -v /dev:/dev -v $(pwd):/mnt goldarte/img-tool:v0.5
|
||||
```
|
||||
|
||||
The image will be located in `images` directory in the clever-show source code directory.
|
||||
The image will be located in `images` directory in the root of clever-show source code directory.
|
||||
|
||||
Article about building custom image is located here:
|
||||
Full documentation about building custom image is located here:
|
||||
* English
|
||||
* [Russian](../docs/ru/image-building.md)
|
||||
|
||||
@@ -105,13 +105,13 @@
|
||||
* Кнопка `Pause/Resume` - ставит на паузу и возобновляет выполнение полётных задач. После каждого нажатия кнопка меняет состояние на обратное.
|
||||
* Состояние`Pause` - ставит на паузу очередь заданий всех выбранных коптеров: приостанавливается выполнение любого полётного задания. Рекомендуется использовать в чрезвычайных ситуациях для определения неисправного коптера. **Внимание!** Данная команда НЕ прерывает полёт коптера в уже указанную точку (например: элементы взлёта, посадки; следование до начальной точки анимации и т.д.)
|
||||
* Состояние `Resume` - все выбранные коптеры *синхронизированно* продолжат выполнение своих очередей заданий (например исполнение анимации)
|
||||
|
||||
#### Средства перехвата в экстренных ситуациях
|
||||
|
||||
* Кнопка `Land selected` - все выбранные коптеры прекращают выполнение своих полётных заданий, очищают очередь заданий и немедленно переходят в режим посадки. **Используйте в экстренных случаях как одно из средств перехвата.**
|
||||
* Кнопка `Land ALL` - ВСЕ коптеры прекращают выполнение своих полётных заданий, очищают очередь заданий и немедленно переходят в режим посадки. **Используйте в экстренных случаях как одно из средств перехвата.**
|
||||
* Кнопка `Land selected` - все выбранные коптеры прекращают выполнение своих полётных заданий, очищают очередь заданий и немедленно переходят в режим посадки AUTO.LAND. **Используйте в экстренных случаях как одно из средств перехвата.**
|
||||
* Кнопка `Land ALL` - ВСЕ коптеры прекращают выполнение своих полётных заданий, очищают очередь заданий и немедленно переходят в режим посадки AUTO.LAND. **Используйте в экстренных случаях как одно из средств перехвата.**
|
||||
|
||||
|
||||
* Кнопка `Emergency land` - прерывает выполнение полётных заданий *ВСЕХ* подключенных коптеров. Сбрасывает очередь заданий - *действие необратимо*. Выполняет полную остановку и немедленную посадку коптеров. **Используйте в экстренных случаях как одно из средств перехвата.**
|
||||
* Кнопка `Emergency land` - все выбранные коптеры прекращают выполнение своих полётных заданий, очищают очередь заданий и немедленно переходят в режим экстренной посадки - на все моторы подаётся небольшая мощность, которая уменьшается через определённое время до нуля. **Используйте в экстренных случаях как одно из средств перехвата.**
|
||||
* Кнопка `Visual land` - открывает диалоговое окно модуля визуальной посадки неисправного коптера. Полное описание находится в [конце статьи](#visual-land).
|
||||
|
||||
* Кнопка `Disarm selected` - все выбранные коптеры прекращают выполнение своих полётных заданий, очищают очередь заданий и немедленно отключают моторы. Это может привести к падению и повреждению коптеров.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Инструкция по настройке и запуску клиента и сервера
|
||||
# Быстрый старт
|
||||
|
||||
## Список оборудования
|
||||
|
||||
@@ -36,7 +36,7 @@ cd ~/clever-show/Drone
|
||||
sudo ./client_setup.sh <SSID> <password> <copter name> <server ip>
|
||||
```
|
||||
|
||||
* Теперь при запуске серверного приложения настроенные коптеры будут отображаться в виде таблицы. Также можно подключаться к Raspberry Pi на коптере по его имени через `ssh` в указанной при настройке wifi сети, например `ssh pi@clever-1`, пароль `raspberry`.
|
||||
* Теперь при запуске серверного приложения настроенные коптеры будут отображаться в виде таблицы. Также можно подключаться к Raspberry Pi на коптере по его имени с добавкой .local через `ssh` в указанной при настройке wifi сети, например `ssh pi@clever-1.local`, пароль `raspberry`. Если у вас установлено приложение samba, то к коптеру можно подключаться по его имени, например `ssh pi@clever-1`.
|
||||
|
||||
Документация по клиентской части находится [здесь](client.md).
|
||||
|
||||
@@ -70,3 +70,15 @@ python3 server_qt.py
|
||||
```
|
||||
|
||||
Документация по серверной части находится [здесь](server.md).
|
||||
|
||||
## Подготовка дрона
|
||||
|
||||
Для запуска анимации все коптеры должны иметь настроенную систему позиционирования. По-умолчанию в образе настроен полёт по optical flow - на коптере должен быть установлер лазерный дальномер, а камера должна быть наклонена вниз шлейфом назад.
|
||||
|
||||
Вы можете настроить один коптер на любую систему позиционирования, которую поддерживает пакет [clever](https://clever.coex.tech/ru/programming.html#positioning), а затем размножить настройки на остальные коптеры с [сервера](server.md#раздел-сервер) с помощью команды `Send launch files`.
|
||||
|
||||
## Подготовка анимации
|
||||
|
||||
* Создайте анимацию объектов в Blender или воспользуйтесь [примерами](../../blender-addon/examples). Условная единица расстояния в Blender конвертируется в метры. Задержка между кадрами по-умолчанию в [настройках коптера](../../Drone/client_config.ini) равна 0.1 секунды (параметр frame_delay в разделе ANIMATION), будьте внимательны при настройке частоты кадров в анимации Blender. Следите за скоростями коптеров, чтобы они были не слишком большими: аддон выдаст предупреждение, но всё равно сконвертирует анимацию.
|
||||
* Сконвертируйте анимацию с помощью [аддона для Blender](blender-addon.md).
|
||||
* Загрузите анимацию с помощью команды `Send animations` на [сервере](server.md#раздел-server).
|
||||
|
||||
Reference in New Issue
Block a user