diff --git a/Drone/README.md b/Drone/README.md new file mode 100644 index 0000000..ca5cb6c --- /dev/null +++ b/Drone/README.md @@ -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) diff --git a/Drone/client.py b/Drone/client.py index 4b55279..391d7eb 100644 --- a/Drone/client.py +++ b/Drone/client.py @@ -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)) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 6369507..3f7e08c 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -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() diff --git a/README.md b/README.md index 31f9d92..7b6909f 100644 --- a/README.md +++ b/README.md @@ -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! [![Build Status](https://travis-ci.org/CopterExpress/clever-show.svg?branch=master)](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 diff --git a/README_RU.md b/README_RU.md index 5659fdc..e0fea14 100644 --- a/README_RU.md +++ b/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, сконвертируйте её в полётные пути дронов, настройте дроны и запустите своё собственное шоу дронов! [![Build Status](https://travis-ci.org/CopterExpress/clever-show.svg?branch=master)](https://travis-ci.org/CopterExpress/clever-show) +## Демонстрационное видео + +[![Autonomous drone show in a theater](http://img.youtube.com/vi/HdHbZFz7nR0/0.jpg)](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). - - diff --git a/Server/README.md b/Server/README.md new file mode 100644 index 0000000..9aeb18c --- /dev/null +++ b/Server/README.md @@ -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) diff --git a/Server/copter_table_models.py b/Server/copter_table_models.py index 92fa729..fd438dc 100644 --- a/Server/copter_table_models.py +++ b/Server/copter_table_models.py @@ -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 diff --git a/Server/server.py b/Server/server.py index 03a2b4b..46d65be 100644 --- a/Server/server.py +++ b/Server/server.py @@ -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) diff --git a/Server/server_qt.py b/Server/server_qt.py index 6580a0b..48b2b69 100644 --- a/Server/server_qt.py +++ b/Server/server_qt.py @@ -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") diff --git a/Server/visual_land_dialog.py b/Server/visual_land_dialog.py new file mode 100644 index 0000000..aed194c --- /dev/null +++ b/Server/visual_land_dialog.py @@ -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() diff --git a/blender-addon/examples/two_drones_test.blend b/blender-addon/examples/two_drones_test.blend new file mode 100644 index 0000000..7a8d5c9 Binary files /dev/null and b/blender-addon/examples/two_drones_test.blend differ diff --git a/builder/README.md b/builder/README.md index 76b5ab1..8b6e2bb 100644 --- a/builder/README.md +++ b/builder/README.md @@ -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) diff --git a/docs/ru/server.md b/docs/ru/server.md index 187358c..b9b011a 100644 --- a/docs/ru/server.md +++ b/docs/ru/server.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` - все выбранные коптеры прекращают выполнение своих полётных заданий, очищают очередь заданий и немедленно отключают моторы. Это может привести к падению и повреждению коптеров. diff --git a/docs/ru/start-tutorial.md b/docs/ru/start-tutorial.md index 795a6af..06b1fc5 100644 --- a/docs/ru/start-tutorial.md +++ b/docs/ru/start-tutorial.md @@ -1,4 +1,4 @@ -# Инструкция по настройке и запуску клиента и сервера +# Быстрый старт ## Список оборудования @@ -36,7 +36,7 @@ cd ~/clever-show/Drone sudo ./client_setup.sh ``` -* Теперь при запуске серверного приложения настроенные коптеры будут отображаться в виде таблицы. Также можно подключаться к 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).