Merge pull request #77 from CopterExpress/renaming_reorganization

Renaming reorganization
This commit is contained in:
Arthur Golubtsov
2020-06-05 23:28:37 +03:00
committed by GitHub
62 changed files with 804 additions and 512 deletions

31
.gitignore vendored
View File

@@ -2,6 +2,7 @@
__pycache__/
*.py[cod]
*$py.class
__init__.py
# Logs
*.log
@@ -17,17 +18,21 @@ images/
show-env/
builder/clever-config
Server/tests.py
Server/convert_ui.sh
Server/config/server.ini
Server/server_logs
Server/testj\.ipynb
Server/tst_client\.py
Server/tst\.py
server/tests.py
server/test
server/server_venv
server/convert_ui.sh
server/config/server.ini
server/server_logs
server/testj\.ipynb
server/tst_client\.py
server/tst\.py
Drone/test_animation/
Drone/animation.csv
Drone/client_logs
Drone/config/client.ini
Drone/_copter_client_old_\.py
Drone/test_cl\.py
drone/test_animation/
drone/test
drone/drone_venv
drone/animation.csv
drone/client_logs
drone/config/client.ini
drone/_copter_client_old_\.py
drone/test_cl\.py

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
__all__ = ['FlightLib', 'LedLib']

View File

@@ -1,7 +0,0 @@
# 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)

View File

@@ -2,9 +2,9 @@
[Русская версия](README_RU.md)
Software for making the drone show controlled by Raspberry Pi, PX4 and COEX [Clever](https://github.com/CopterExpress/clever) package.
Software for making the drone show with drones controlled by [Raspberry Pi](https://www.raspberrypi.org/) with COEX [Clover](https://github.com/CopterExpress/clover) package and flight controller with [PX4](https://github.com/PX4/Firmware) firmware.
Create animation in Blender, convert it to drone paths, set up the drones and run your own show!
Create animation in [Blender](https://www.blender.org/), 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)
@@ -16,9 +16,9 @@ Create animation in Blender, convert it to drone paths, set up the drones and ru
## This software includes
* [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 exporting animation to drone paths
* [Drone side](drone/) for remote synchronized control of drones with emergency drone protection module
* [Server side](server/) for making the drone show with ability of tuning drones, animation and music
* [Blender 2.8 addon](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
@@ -27,4 +27,4 @@ Create animation in Blender, convert it to drone paths, set up the drones and ru
Start tutorial is located [here](docs/ru/start-tutorial.md).
Detailed documentation is located in the [docs](https://github.com/CopterExpress/clever-show/tree/master/docs) folder.
Detailed documentation is located in the [docs](docs/) folder.

View File

@@ -2,9 +2,9 @@
[English version](README.md)
Програмное обеспечение для запуска шоу дронов под управлением Raspberry Pi с пакетом COEX [Clever](https://github.com/CopterExpress/clever) и полётного контроллера с прошивкой PX4.
Програмное обеспечение для запуска шоу дронов под управлением [Raspberry Pi](https://www.raspberrypi.org/) с пакетом COEX [Clover](https://github.com/CopterExpress/clover) и полётным контроллером с прошивкой [PX4](https://github.com/PX4/Firmware).
Создайте анимацию в Blender, сконвертируйте её в полётные пути дронов, настройте дроны и запустите своё собственное шоу дронов!
Создайте анимацию в [Blender](https://www.blender.org/), сконвертируйте её в полётные пути дронов, настройте дроны и запустите своё собственное шоу дронов!
[![Build Status](https://travis-ci.org/CopterExpress/clever-show.svg?branch=master)](https://travis-ci.org/CopterExpress/clever-show)
@@ -16,13 +16,13 @@
## Пакет включает в себя
* [Набор ПО для дрона](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, в файлы полётов для каждого коптера
* [Набор ПО для дрона](drone/) с клиентским приложением для удаленного синхронизированного управления дронами и модулем экстренной посадки.
* [Серверное приложение](server/) для создания шоу и настройки дронов, анимации и музыки
* [Аддон для Blender 2.8](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).
Подробная документация расположена в папке [docs](docs/).

View File

@@ -1,7 +0,0 @@
# 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)

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -4,15 +4,15 @@
## Установка и настройка
* Скачайте и установите согласно инструкциям последнюю версию Blender 2.81 с [оффициального сайта](https://www.blender.org/download/).
* Откройте Blender, в верхнем меню выберите `Edit > Preferences`. В открывшемся окне настроек в боковой панели выберите пункт `Add-ons`. Нажмите на кнопку `Install...` в верхнем правом углу окна. В диалоговом окне откройте путь к папке с аддоном [clever-show/blender-addon](../../blender-addon/) и выберите файл `addon.py`. Нажмите `Install Add-on from file...`. Аддон установлен.
* После установки аддона поставьте "галочку" напротив аддона `Import-Export: Export > CSV Drone Swarm Animation Exporter` для активации аддона.
* Скачайте и установите согласно инструкциям последнюю версию Blender 2.8 с [оффициального сайта](https://www.blender.org/download/).
* Откройте Blender, в верхнем меню выберите `Edit > Preferences`. В открывшемся окне настроек в боковой панели выберите пункт `Add-ons`. Нажмите на кнопку `Install...` в правом верхнем углу окна. В диалоговом окне откройте путь к папке с аддоном [clever-show/blender-addon](../../blender-addon/) и выберите файл `addon.py`. Нажмите `Install Add-on from file...`. Аддон установлен.
* После установки аддона поставьте "галочку" напротив аддона `Import-Export: clever-show animation (.csv)` для активации аддона.
Аддон активирован и готов к работе. Выполнение этих операций не понадобится при дальнейших запусках Blender.
## Экпорт при помощи аддона
Для вызова диалогового окна экспорта нажмите в верхнем меню `File > Export > CSV Drone Swarm Animation Exporter`. В открывшемся окне экспорта необходимо выбрать целевой путь экспорта и название папки, которую создаст аддон в процессе экспорта. В боковом меню доступна панель параметров экспорта:
Для вызова диалогового окна экспорта нажмите в верхнем меню `File > Export > clever-show animation (.csv)`. В открывшемся окне экспорта необходимо выбрать целевой путь экспорта и название папки, которую создаст аддон в процессе экспорта. В боковом меню доступна панель параметров экспорта:
* `Use name filter for objects` - чекбокс, определяет, будет ли использован фильтр объектов при сохранении их путей. При отключении этого параметра будут экспортированы пути всех видимых объектов.
* `Name identifier` - фильтр имён объектов. Если активен чекбокс `Use name filter for objects`, будут сохранены только пути объектов, содержащих данное значение в названии.
@@ -20,7 +20,7 @@
* `Speed limit` - при нарушении указанного ограничения по скорости передвижения дронов будут выведены предупреждения.
* `Distance limit` - при нарушении указанной минимальной дистанции между дронами будут выведены предупреждения.
После настройки нужных параметров нажмите кнопку `Export Drone Swarm animation`. В указанную папку экспортируются анимации указанных объектов из проекта Blender в формате `.csv`.
После настройки нужных параметров нажмите кнопку `Export clever-show animation`. В указанную папку экспортируются анимации указанных объектов из проекта Blender в формате `.csv`.
## Деактивация и удаление

View File

@@ -2,29 +2,48 @@
Приложение для удаленного синхронизированного управления дронами в шоу и модуль экстренной защиты дронов.
* `client.py` - основной модуль связи и управления дроном
* `failsafe.py` - модуль защиты дронов, который создает сервис `/emergency_land` и контролирует состояние дрона в соответствии с логикой, указанной в пунктах `[FAILSAFE]` и `[EMERGENCY LAND]` файла настроек `client.ini`
## Инструкции
* [Установка и запуск](start-tutorial.md#установка-и-запуск-клиента)
* [Настройка клиента](#настройка-клиента)
## Схема работы клиента
## Схема работы клиента в образе для Raspberry Pi
Клиент является сервисом `clever-show` в операционной системе коптера. Сервис запускает скрипт [copter_client.py](../../Drone/copter_client.py) и автоматически запускается при загрузке операционной системы. В случае необходимости применения параметров обновлённой конфигурации клиента сервис может быть перезапущен. Сервис `clever-show` предназначен для управления и настройки коптера для группового полёта с помощью приложения сервера.
Клиент является сервисом `clever-show` в операционной системе коптера. Сервис запускает скрипт [client.py](../../drone/client.py) и автоматически запускается при загрузке операционной системы. В случае необходимости применения параметров обновлённой конфигурации клиента сервис может быть перезапущен. Сервис `clever-show` предназначен для управления и настройки коптера для группового полёта с помощью приложения сервера.
Вместе с клиентом в операционной системе зарегистрирован сервис экстренной защиты дрона `visual_pose_watchdog`. Данный сервис запускает скрипт [visual_pose_watchdog.py](../../Drone/visual_pose_watchdog.py) и автоматически запускается при загрузке операционной системы. В случае необходимости применения параметров обновлённой конфигурации клиента сервис может быть перезапущен. Сервис `visual_pose_watchdog` предназначен для автоматической посадки коптера в экстренных ситуациях: при отсутствии сообщений о позиции дрона из топика `/mavros/vision_pose/pose` и при столкновении с объектами в полёте, когда расстояние между текущей точкой, где находится коптер, и точкой, где ему следует находиться по полётному заданию, превышает пороговое значение. Также `visual_pose_watchdog` предоставляет ROS сервис `/emergency_land`, к которому может обратиться клиент по команде с сервера для экстренной посадки.
Вместе с клиентом в операционной системе зарегистрирован сервис экстренной защиты дрона `failsafe`. Данный сервис запускает скрипт [failsafe.py](../../drone/failsafe.py) и автоматически запускается при загрузке операционной системы. В случае необходимости применения параметров обновлённой конфигурации клиента сервис может быть перезапущен.
Логи обоих сервисов записываются в файл `/var/log/syslog` операционной системы бортового компьютера Raspberry Pi на дроне. Логи текущего сеанса доступны для просмотра при выполнении команд `journalctl -u clever-show` для клиента и `journalctl -u visual_pose_watchdog` для сервиса экстренной защиты дрона в терминале, подключенном к Raspberry Pi. Логи могут быть полезны при анализе возникших нештатных или аварийных ситуаций при полёте коптера под управлением приложения клиента.
Сервис `failsafe` предназначен для автоматической посадки коптера в экстренных ситуациях:
* при отсутствии сообщений о позиции дрона из топика `/mavros/vision_pose/pose`
* при столкновении с объектами в полёте, когда расстояние между текущей точкой, где находится коптер, и точкой, где ему следует находиться по полётному заданию, превышает пороговое значение.
Также `failsafe` предоставляет ROS сервис `/emergency_land`, к которому может обратиться клиент по команде с сервера для экстренной посадки дрона.
Логи обоих сервисов записываются в файл `/var/log/syslog` операционной системы бортового компьютера Raspberry Pi на дроне. Логи текущего сеанса доступны для просмотра при выполнении команд `journalctl -u clever-show` для клиента и `journalctl -u failsafe` для сервиса экстренной защиты дрона в терминале, подключенном к Raspberry Pi. Логи могут быть полезны при анализе возникших нештатных или аварийных ситуаций при полёте коптера под управлением приложения клиента.
## Настройка клиента
### Файл конфигурации
Конфигурация клиента создаётся согласно [спецификации](../../Drone/config/spec/configspec_client.ini), в ней можно посмотреть значения по умолчанию для любого параметра после ключевого слова `default`. Все изменения сохраняются в файл конфигурации `client.ini` в папке `clever-show/Drone/config`. Доступно редактирование конфигурации клиента через GUI модуль `Config editor` в приложении сервера. Для редактирования конфигурации с сервера нужно выбрать строку с клиентом, для которого требуется изменение конфигурации, кликнуть левой кнопкой мыши по любой ячейке из строки и выбрать `Edit config` из контекстного меню.
Конфигурация клиента создаётся согласно [спецификации](../../drone/config/spec/configspec_client.ini), в ней можно посмотреть значения по умолчанию для любого параметра после ключевого слова `default`. Все изменения сохраняются в файл конфигурации `client.ini` в папке `clever-show/drone/config`. Доступно редактирование конфигурации клиента через GUI модуль `Config editor` в приложении сервера. Для редактирования конфигурации с сервера нужно выбрать строку с клиентом, для которого требуется изменение конфигурации, кликнуть левой кнопкой мыши по любой ячейке из строки и выбрать `Edit config` из контекстного меню.
Конфигурация по умолчанию является полностью работоспособной и не требует изменений для быстрого старта клиента.
Для централизованной загрузки конфигурации на все коптеры нужно использовать пункт меню `Send configurations` на [сервере](server.md#раздел-server). Допускается загрузка неполного файла параметров конфигурации, с отсутствующими разделами или параметрами относительно конфигурации по умолчанию. Также доступна опция загрузки конфигурации конкретного клиента на выделенные коптеры: для этого нужно с сервера выбрать строку с клиентом, с которого требуется скопировать конфигурацию на выделенные клиенты, кликнуть левой кнопкой мыши по любой ячейке из строки и выбрать `Copy config to selected` из контекстного меню.
Для централизованной загрузки конфигурации на все коптеры нужно использовать пункт меню `Send configurations` на [сервере](server.md#раздел-server). Допускается загрузка неполного файла параметров конфигурации, с отсутствующими разделами или параметрами относительно конфигурации по умолчанию. Также доступна опция загрузки конфигурации конкретного клиента на выделенные коптеры: для этого нужно в приложении сервера выбрать строку с клиентом, с которого требуется скопировать конфигурацию на выделенные клиенты, кликнуть левой кнопкой мыши по любой ячейке из строки и выбрать `Copy config to selected` из контекстного меню.
### Описание параметров
#### Основной раздел
* `config_name` - название конфигурации
* `config_version` - версия конфигурации
* `id` - имя коптера, отображаемое в таблице. Если значение `/hostname` - имя определяется из файла `/etc/hostname`.
* `clover_dir` - путь к директории с ROS пакетом [сlover](https://github.com/CopterExpress/clover). Необходим для загрузки файлов с картами aruco меток и launch файлов настройки запуска сервиса `clover`. Если значение `auto` - клиент пытается сам определить нужную директорию пакета `clover` (или `clever`) через `rospkg` при первом запуске. Если директорию с пакетом не удаётся определить, значение устанавливается в `error`, а файлы, специфичные для настройки ROS пакета `сlover` не передаются с сервера на клиент.
#### Раздел SERVER
В этом разделе задаются параметры сетевого взаимодействия клиента с сервером. Доступны следующие параметры:
@@ -46,11 +65,87 @@
* `transmit` - логическое значение, определяет, нужно ли передавать данные на сервер.
* `frequency` - частота передачи данных на сервер, целочисленное значение, количество раз в секунду.
* `log_resources` - логическое значение, определяет, будет ли записываться в лог сервиса клиента clever-show состояние бортового компьютера Raspberry Pi: загрузка процессора и оперативной памяти, температура процессора, состояние температуры, состояние системы питания.
* `log_resources` - логическое значение, определяет, будет ли записываться в лог сервиса клиента clever-show состояние бортового компьютера: загрузка процессора и оперативной памяти, температура процессора, состояние температуры, состояние системы питания (только для Raspberry Pi).
#### Раздел POSITION WATCHDOG
#### Раздел FLIGHT
В данном разделе настраивается программа экстренной защиты коптера от потери позиции или столкновения с объектом.
В данном разделе находятся настройки, влияющие на процесс полёта коптера.
* `frame_id` - название системы координат, относительно которой будут публиковаться координаты точек для воспроизведения анимации. Если значение `floor` - клиент публикует статическую систему координат с названием `floor` и настройками из раздела [FLOOR_FRAME](#раздел-floor_frame). **Внимание!** Убедитесь, что коптер удерживает позицию в данной системе координат. Для этого вы можете воспользоваться командой [Takeoff](server.md#тестовые-команды) из серверного приложения. Коптер взлетит на высоту `takeoff_height` относительно текущей.
* `takeoff_height` - высота взлёта коптера, в метрах. Используется в начале воспроизведения анимации или при тестировании коптера с сервера.
* `takeoff_time` - максимальное время взлёта коптера, в секундах.
* `reach_first_point_time` - максимальное время полёта к первой точке анимации, в секундах.
* `land_time` - время зависания в конечной точке анимации перед посадкой, в секундах.
* `land_timeout` - время таймаута посадки, после которого происходит выключение моторов коптера, в секундах.
#### Раздел FLOOR FRAME
Данный раздел описывает смещение системы координат с названием `floor` и используется только при указании параметра `frame_id` как `floor` в разделе [COPTERS](#раздел-copters).
* `enabled` - логическое значение, определяет, нужно ли публиковать фрейм `floor`
* `parent` - название опорной системы координат, относительно которой будет располагаться система координат `floor`.
* `translation` - смещение системы координат `floor` по осям (x, y, z) относительно системы координат `parent`, в метрах.
* `rotation` - поворот системы координат `floor` на углы (roll, pitch, yaw) вокруг осей (x, y, z) относительно системы координат `parent`, в градусах.
**Внимание!** Повороты `roll`, `pitch`, `yaw` производятся последовательно в указанном порядке.
#### Раздел GPS FRAME
Данный раздел описывает создание системы координат с названием `gps`. Начальное положение этой системы координат лежит в точке с координатами `lat` (latitude, широта), `lon` (longitude, долгота). Угол поворота задаётся значением `yaw` (в градусах) поворотом оси z начального положения коптера в системе координат `map`.
* `lat` - широта в градусах в системе WGS 84
* `lon` - долгота в градусах в системе WGS 84
* `yaw` - угол поворота системы координат в градусах
#### Раздел ANIMATION
В данном разделе настраивается обработка анимации. За обработку анимации отвечает отдельный модуль [animation](../../drone/modules/animation.py). При загрузке анимации на коптер модуль производит разделение последовательности кадров анимации на 5 ключевых этапов:
1. Коптер неподвижен в начале анимации - `static_begin`
2. Коптер взлетает - `takeoff`
3. Коптер следует по маршруту анимации - `route`
4. Коптер выполняет посадку - `land`
5. Коптер неподвижен до завершения файла анимации - `static_end`
Кадр анимации - это набор данных, необходимых для позиционирования коптера и определения цвета его подсветки. В текущей версии ПО кадр анимации представлен последовательностью чисел `x y z yaw r g b` в строке `.csv` файла с анимацией, где:
* `x`, `y`, `z` - координаты коптера в текущем кадре в метрах
* `yaw` - рыскание коптера в радианах
* `r`, `g`, `b` - компоненты цвета подсветки коптера, целые числа от 0 до 255
После разделения анимации на ключевые этапы модуль формирует выходную последовательность кадров, определяющих положение коптера и цвет его подсветки, а также последовательность действий при полёте к первой точке анимации. Настройка модуля производится с помощью следующих параметров:
* `start_action` - первое действие при запуске воспроизведения анимации. Доступные варианты:
* `auto` - автоматический выбор действия между `takeoff` (взлёт) или `fly` (мгновенный полёт по точкам) на основе текущего уровня высоты коптера. Если (`z` в начальной точке анимации) - (текущая высота коптера) > (уровень взлета `takeoff_level`), то значение устанавливается в `takeoff`, иначе значение устанавливается в `fly`.
* `takeoff` - выполнение *логики полёта к первой точке*.
* `fly` - выполнение *логики немедленного полёта*
Если в файле анимации коптер взлетает с земли, при старте анимации будет применена *логика немедленного воспроизведения*: коптер включает двигатели через время, которое требуется для выполнения начальных кадров анимации, которые не включены в выходную последовательность, затем через время `arming_time` начинает следовать точкам, указанным в анимации. Если в файле анимации коптер начинает полёт в воздухе, при старте анимации будет применена *логика полёта к первой точке*: через время, которое требуется для выполнения начальных кадров анимации, которые не включены в выходную последовательность, коптер в начале взлетает на высоту `takeoff_height` за время `takeoff_time`, затем перемещается к первой точке за время `reach_first_point_time`, и затем начинает следовать точкам, указанным в анимации.
* `takeoff_level` - уровень взлёта для автоматического определения первого действия коптера при старте анимации
* `ground_level` - уровень земли, используется для проверки, не попытается ли коптер улететь под землю при воспроизведении анимации. Доступные варианты настройки:
* `current` - за уровень земли принимается текущий уровень высоты коптера перед стартом.
* координата z в метрах
* `frame_delay` - время воспроизведения одного кадра в секундах.
* `yaw` - поворот коптера при полёте по точкам, в градусах. Если значение `nan` - коптер сохраняет изначальную ориентацию в полёте. Если значение `animation` - коптер берёт поворот по yaw из файла с анимацией.
* `ratio` - масштаб анимации (ratio_x, ratio_y, ratio_z) по осям (x, y, z)
* `common_offset` - смещение анимации относительно текущей системы, общее для всех коптеров, в метрах. Список из 3 величин (x, y, z): каждая величина задаёт смещение по соответствующей оси.
* `private_offset` - смещение анимации относительно текущей системы, только для данного коптера, в метрах. Список из 3 величин (x, y, z): каждая величина задаёт смещение по соответствующей оси.
* `[[OUTPUT]]` - флаги, определяющие, какие этапы будут включены в выходную последовательность кадров.
#### Раздел LED
Настройки адресуемой светодиодной ленты на коптере
* `use` - логическое значение, определяет, используется или нет светодиодная лента.
* `pin` - номер пина GPIO на Raspberry, к которому подключается лента
* `count` - количество задействованных светодиодов в ленте
* `takeoff_indication` - логическое значение, определяет, использовать ли подсветку для обозначения взлёта
* `land_indication` - логическое значение, определяет, использовать ли подсветку для обозначения посадки
#### Раздел FAILSAFE
В данном разделе настраивается программа экстренной защиты коптера от потери позиции или столкновения с объектом. Модуль `failsafe` при запуске предоставляет сервис `/emergency_land`, его настройки находятся в следующем разделе [EMERGENCY LAND](#раздел-emergency-land).
* `enabled` - логическое значение, определяет, использовать или нет экстренную защиту при потере визуальной позиции или столкновении с объектом.
* `log_state` - логическое значение, определяет, будет ли записываться в лог сервиса состояние коптера: `armed: {} | mode: {} | vis_dt: {:.2f} | pos_delta: {:.2f} | pos_dt: {:.2f} | range: {:.2f} | watchdog_action: {}`.
@@ -67,55 +162,6 @@
* `thrust` - начальная мощность, подаваемая на моторы в случае выбора действия `emergency_land` при срабатывании экстренной защиты. Безразмерная величина, от 0 (отсутствие мошности) до 1 (полная мощность). Для гарантированной посадки рекомендуется устанавливать в значение, меньшее по величине на 5-10 процентов, чем газ висения (параметр `MPC_THR_HOVER` в px4). **Внимание!** Неправильная настройка этого параметра может привести к взлёту коптера вверх вместо посадки!
* `decrease_thrust_after` - время, через которое мощность на моторах плавно начинает уменьшаться в случае выбора действия `emergency_land` при срабатывании экстренной защиты, в секундах.
#### Раздел COPTER
В данном разделе находятся настройки, влияющие на процесс полёта коптера.
* `frame_id` - название системы координат, относительно которой будут публиковаться координаты точек для воспроизведения анимации. Если значение `floor` - клиент публикует статическую систему координат с названием `floor` и настройками из раздела [FLOOR_FRAME](#раздел-floor_frame). **Внимание!** Убедитесь, что коптер удерживает позицию в данной системе координат. Для этого вы можете воспользоваться командой [Takeoff](server.md#тестовые-команды) из серверного приложения. Коптер взлетит на высоту `takeoff_height` относительно текущей.
* `takeoff_height` - высота взлёта коптера, в метрах. Используется в начале воспроизведения анимации или при тестировании коптера с сервера.
* `takeoff_time` - максимальное время взлёта коптера, в секундах.
* `safe_takeoff` - логическое значение, определяет, нужно ли производить посадку в безопасном режиме.
* `reach_first_point_time` - максимальное время полёта к первой точке анимации, в секундах.
* `land_time` - время зависания в конечной точке анимации перед посадкой, в секундах.
* `land_timeout` - время таймаута посадки, после которого происходит выключение моторов коптера, в секундах.
* `common_offset` - смещение координат относительно текущей системы, общее для всех коптеров, в метрах. Список из 3 величин (x, y, z): каждая величина задаёт смещение по соответствующей оси.
#### Раздел FLOOR_FRAME
Данный раздел описывает смещение системы координат с названием `floor` и используется только при указании параметра `frame_id` как `floor` в разделе [COPTERS](#раздел-copters).
* `enabled` - логическое значение, определяет, нужно ли публиковать фрейм `floor`
* `parent` - название опорной системы координат, относительно которой будет располагаться система координат `floor`.
* `translation` - смещение системы координат `floor` по осям (x, y, z) относительно системы координат `parent`, в метрах.
* `rotation` - поворот системы координат `floor` на углы (roll, pitch, yaw) вокруг осей (x, y, z) относительно системы координат `parent`, в градусах.
**Внимание!** Повороты `roll`, `pitch`, `yaw` производятся последовательно в указанном порядке.
#### Раздел ANIMATION
В данном разделе настраивается обработка анимации.
* `takeoff_detection` - логическое значение, определяет, будет ли производиться автоматическая обработка старта анимации. **Если значение True**, при загрузке анимации проверяется взлёт коптеров. Если в файле анимации коптер взлетает с земли, при старте анимации будет применена *логика немедленного воспроизведения*: коптер сразу начинает следовать точкам, указанным в анимации. Если в файле анимации коптер начинает полёт в воздухе, при старте анимации будет применена *логика полёта к первой точке*: коптер в начале взлетает на высоту `takeoff_height` за время `takeoff_time`, затем перемещается к первой точке за время `reach_first_point_time`, и затем начинает следовать точкам, указанным в анимации. **Если значение False**, при загрузке анимации не проверяется взлёт коптеров, а при старте анимации действует *логика полёта к первой точке*.
* `land_detection` - логическое значение, определяет, будет ли производиться автоматическая обработка завершения анимации. **Если значение True**, при загрузке анимации проверяется посадка коптеров. Если в файле анимации коптер садится на землю и стоит до завершения анимации, проверка удалит все точки в анимации после начала посадки коптера. Таким образом, коптер в конце анимации зависнет над точкой посадки на время `land_time`, сядет автоматически и выключит моторы. **Если значение False**, при загрузке анимации не проверяется посадка коптеров и точкой посадки считается последняя точка в анимации. Например, если анимация посадки нарисована полностью и коптер стоит после посадки на земле некоторое время, а значение данного параметра **False**, всё это время у коптера будут включены моторы и он будет пытаться удержать указанную позицию посадки вплоть до завершении файла анимации, затем через время `land_time` перейдёт в редим посадки.
* `frame_delay` - время воспроизведения одного кадра в секундах.
* `ratio` - масштаб анимации (ratio_x, ratio_y, ratio_z) по осям (x, y, z)
* `yaw` - поворот коптера при полёте по точкам, в градусах. Если значение `nan` - коптер сохраняет изначальную ориентацию в полёте. Если значение `animation` - коптер берёт поворот по yaw из файла с анимацией.
#### Раздел LED
Настройки адресуемой светодиодной ленты на коптере
* `use` - логическое значение, определяет, используется или нет светодиодная лента.
* `pin` - номер пина GPIO на Raspberry, к которому подключается лента
* `count` - количество задействованных светодиодов в ленте
#### Раздел PRIVATE
В данном разделе находятся параметры, специфичные для конкретного коптера.
* `id` - имя коптера, отображаемое в таблице. Если значение `/hostname` - имя определяется из файла `/etc/hostname`.
* `offset` - смещение в метрах по осям (x, y, z) относительно текущей системы координат, только для данного коптера.
#### Раздел SYSTEM
Системные настройки служебных команд клиента

View File

@@ -46,7 +46,7 @@
* `checks` - состояние самодиагностики коптера. Ячейка в данном столбце проходит проверку, если её значение `OK`. В остальных случаях, если ячейка не пустая, она не проходит проверку.
* При двойном клике на ячейку при наличии ошибок будет показано диалоговое окно с полной детализацией всех ошибок.
* `current x y z yaw frame_id` - текущее положение коптера с указанием названия системы координат. Ячейка автоматически проходит проверку если у параметра [check_current_position](#раздел-checks) установлено значение `false`. Иначе, ячейка в данном столбце не проходит проверку, если её значение `NO_POS` или содержит `nan`. В остальных случаях, если ячейка не пустая, она проходит проверку.
* `start x y z` - стартовое положение коптера для воспроизведения анимации. Ячейка в данном столбце не проходит проверку, если её значение `NO_POS` или разница между текущим и стартовым положением коптера больше значения [start_pos_delta_max](#раздел-checks). В остальных случаях, если ячейка не пустая, она проходит проверку.
* `start x y z action delay` - стартовое положение коптера для воспроизведения анимации, первое действие при воспроизведении анимации и время через которое выполнится первое действие после старта анимации. Ячейка в данном столбце не проходит проверку, если её значение `NO_POS`, разница между текущим и стартовым положением коптера больше значения [start_pos_delta_max](#раздел-checks) или модуль анимации клиента выдаёт ошибку при обработке анимации и проверке того, что все точки анимации находятся над уровнем земли. В остальных случаях, если ячейка не пустая, она проходит проверку.
* `dt` - разница между временем на сервере и клиенте в секундах, включая сетевую задержку. Ячейка в данном столбце проходит проверку, если её значение меньше значения [time_delta_max](#раздел-checks), задаваемого в настройках сервера. В остальных случаях, если ячейка не пустая, она не проходит проверку. При слишком больших значениях сигнализирует об отсутствии синхронизации времени между коптером и клиентом.
### Меню
@@ -59,27 +59,37 @@
* Подраздел `Send`
* `Animations` - отправка файлов анимации, экспортированных аддоном к Blender, на выбранные коптеры. В диалоговом окне необходимо выбрать *папку*, содержащую файлы анимации. Каждый файл анимации будет отправлен на клиент с именем, соответствующим имени файла без расширения.
`<clever-show>` - расположение ПО `clever-show` на клиенте.
`<clover_dir>` - расположение ROS пакета `clover` на клиенте.
* `Animations` - отправка файлов анимации, экспортированных аддоном к Blender, на выбранные коптеры. В диалоговом окне необходимо выбрать *папку*, содержащую файлы анимации. Каждый файл анимации будет отправлен на клиент с именем, соответствующим имени файла без расширения. На клиенте файл будет сохранён как `<clever-show>/drone/animation.csv`.
* `Camera calibrations` - отправка yaml-файлов калибровки камеры для сервиса `clover`. В диалоговом окне необходимо выбрать *папку*, содержащую файлы конфигурации с расширением `.yaml`. Каждый файл калибровки будет отправлен на клиент с именем (copter ID), соответствующим имени файла без расширения. На клиенте файл будет сохранён как `<clover_dir>/camera_info/calibration.yaml`. **Внимание!** Существующий файл калибровки на коптере будет перезаписан.
---
* `Aruco map` - отправка *единого* файла карты aruco маркеров на все выбранные клиенты. В диалоговом окне необходимо выбрать *один* файл карты в установленном формате. Файл на клиенте будет перезаписан и сохранён как `<clover_dir>/../aruco_pose/map/animation_map.txt`. После получения и записи файла клиент автоматически перезапустит сервис `clover`. Для возобновления работоспособности полётных функций и получения некоторых значений телеметрии *необходимо подождать* некоторое время до полного запуска сервиса.
* `Animation` - отправка *единого* файла анимации на все выбранные клиенты. В диалоговом окне необходимо выбрать *один* файл анимации. На клиенте файл будет сохранён как `<clever-show>/drone/animation.csv`. Полезная функция для быстрого тестирования нескольких дронов, если использовать в комбинации с `Set start X Y to current position`.
* `Configuration` - отправка *единого* файла конфигурации клиента на все выбранные клиенты. В диалоговом окне необходимо выбрать *один* файл конфигурации в установленном формате. Файл конфигурации может быть неполным, в таком случае будут перезаписаны лишь указанные в файле параметры. **Внимание!** Не рекомендуется использовать данное действие для массовой перезаписи `copter ID`, кроме значения `/hostname`. **Внимание!** НЕ отправляйте на клиенты файл конфигурации сервера.
* `Launch files` - отправка launch-файлов конфигурации сервиса `clever`. В диалоговом окне необходимо выбрать *папку*, содержащую файлы конфигурации с сширением `.launch`. Все файлы с таким расширением будут отправлены *на каждый* из клиентов. **Внимание!** Существующие файлы конфигурации на коптерах будут перезаписаны, однако файлы, не отправленные сервером, не будут удалены или модифицированы.
* `Launch files folder` - отправка launch-файлов конфигурации сервиса `clover`. В диалоговом окне необходимо выбрать *папку*, содержащую файлы конфигурации с расширением `.launch` и `.yaml`. Все файлы с таким расширением будут отправлены *на каждый* из клиентов в директорию `<clover_dir>/launch`. **Внимание!** Существующие файлы конфигурации на коптерах будут перезаписаны, однако файлы, не отправленные сервером, не будут удалены или модифицированы.
* `Aruco map` - отправка *единого* файла карты aruco маркеров на все выбранные клиенты. В диалоговом окне необходимо выбрать *один* файл карты в установленном формате. Файл на клиенте будет перезаписан. После получения и записи файла клиент автоматически перезапустит сервис `clever`. Для возобновления работоспособности полётных функций и получения некоторых значений телеметрии *необходимо подождать* некоторое время до полного запуска сервиса.
* `FCU parameters file` - отправка и запись *единого* файла конфигураций полётного контроллера (FCU) на все выбранные клиенты. В диалоговом окне необходимо выбрать *один* файл параметров в установленном формате. Параметры на полётном контроллере будут перезаписаны.
* `Camera calibrations` - отправка yaml-файлов калибровки камеры для сервиса `clever`. В диалоговом окне необходимо выбрать *папку*, содержащую файлы конфигурации с расширением `.yaml`. Каждый файл калибровки будет отправлен на клиент с именем (copter ID), соответствующим имени файла без расширения. **Внимание!** Существующий файл калибровки на коптере будет перезаписан.
* `FCU parameters` - отправка и запись *единого* файла конфигураций полётного контроллера (FCU) на все выбранные клиенты. В диалоговом окне необходимо выбрать *один* файл параметров в установленном формате. Параметры на полётном контроллере будут перезаписаны.
---
* `File` - отправка *одного* любого файла на все выбранные клиенты. В диалоговом окне необходимо выбрать *один* файл. Далее, необходимо указать путь, по которому данный файл будет записан на клиенты (не включая имя файла).
* `Command` - отправка и выполнение любой команды терминала на все выбранные клиенты. В диалоговом окне необходимо ввести требуемую команду. Команды *могут* использовать `sudo`-права.
------
---
* `Retrive file` - позволяет скачать любой файл с клиентов в выбранную директорию в файловой системе сервера. Если при скачивании был выбран более чем один клиент, то к имени файла от каждого клиента будет добавлен его ID. В диалоговом окне сначала введите путь к требуемому файлу на клиенте. Далее, в диалоговом окне необходимо указать путь, по которому данный файл будет записан на сервер.
------
---
* Подраздел `Restart Service`
@@ -89,7 +99,7 @@
* `clever` - перезапускает сервис `clever` на выбранных клиентах. Для возобновления работоспособности полётных функций и получения некоторых значений телеметрии *необходимо подождать* некоторое время до полного запуска сервиса.
* `clever-show` - перезапускает сервис шоу коптеров `clever-show` на выбранных клиентах. Во время перезапуска клиенты будут отключены.
------
---
* `Set start X Y to current position` - устанавливает точку старта анимации у выбранных клиентов в значения текущей позиции по X Y.
@@ -97,9 +107,15 @@
* `Set Z offfset to ground` - устанавливает собственный отступ по Z каждого из выбранных клиентов в значение, равное текущему положению по координате Z. Можно применять для выравнимания общей высоты полёта коптеров.
* `Reset Z offfset` - устанавливает собственный отступ по Z каждого из выбранных клиентов в значение `0`.
---
* `Developer mode`: **Внимание!** Используйте данные действия с большой осторожностью.
* `Update clever-show git` - обновляет папку репозитория `clever-show` на выбранных клиентах. Файлы конфигурации клиента *не будут* перезаписаны. **Внимание!** Для того, чтобы изменения вступили в силу, *необходимо* перезапустить сервис `clever-show`.
---
* `Reboot` - полностью перезагружает полётный контроллер и компьютер на выбранных коптерах. Во время перезапуска клиенты будут отключены.
#### Раздел Server
@@ -112,12 +128,14 @@
* `Play music` - воспроизводит загруженную музыку.
* `Stop music` - останавливает воспроизведение проигрываемой музыки.
------
---
* `Edit server config` - открывает [встроенный редактор конфигурационных файлов](#config-editor) с текущей конфигурацией сервера для редактирования. Доступен чекбокс `Restart` - в случае, если он нажат, то при сохранении конфигурации сервер будет перезапущен. **Внимание!** Изменённые параметры конфигурации будут применены к серверу только после его перезапуска (ручного или автоматического).
* `Edit any config` - открывает [встроенный редактор конфигурационных файлов](#config-editor) и позволяет выбрать для редактирования в файловой системе любой файл конфигурации c расширением `.ini` или же открыть файл спецификации конфигурации для создания файла конфигурации на его основе.
---
* `Restart server` - полностью перезапускает сервер. **Внимание!** После перезапуска сервер более не будет соединён с консолью, из которой был запущен, если сервер изначально был запущен из консоли.
#### Раздел Table
@@ -127,7 +145,13 @@
* `Toggle select` (`Ctrl+A`) - выделят все коптеры\снимает выделение со всех коптеров. Если в таблице выбраны не все коптеры, то *выделяет все* коптеры. Иначе (если были выделены все коптеры) *снимает выделение* со всех коптеров.
* `Select all` - выделят все коптеры в таблице.
* `Deselect all` - снимает выделение со всех коптеров в таблице.
---
* `Remove selected drones` - удаляет выбранные коптеры из таблицы. **Внимание!** В случае, если клиент был подключен, будет произведено отключение. В случае если удалённый таким образом клиент исправно функционировал, он переподключится в кратчайшие сроки.
---
* `Configure columns` - открывает [встроенный конфигуратор](#column-preset-editor) наборов настроек столбцов таблицы.
### Боковая панель команд
@@ -200,18 +224,6 @@
* `port` - TCP порт, на который будут приниматься входящие соединения от клиентов. При использовании broadcast данный порт будет сконфигурирован у клиента автоматически. *Рекомендуется изменить значение по умолчанию в целях безопасности* (любое пятизначное и более число, если другое ПО не использует выбранный порт).
* `buffer_size` - размер буфера при приёме и передаче данных. *Не рекомендуется изменять. Рекомендуется использовать единое значение у сервера и клиентов.*
#### Раздел TABLE
* `remove_disconnected` - Определяет поведение при разрыве связи с клиентом. При значении `True` вся информация о клиенте *будет удалена* как из внутренней памяти, так и *из таблицы*. *Это может привести к 'скачкам' таблицы при отключении клиентов.* При значении `False` отключённые клиенты *не будут* удалены из таблицы, но будут отображены с подсвечиванием ячейки в столбце `copter ID` красным цветом. Все данные будут сохранены. При переподключении клиента, он будет ассоциирован с той же строкой таблицы, а ячейка со значением `copter ID` вновь станет зелёного цвета.
##### Подраздел PRESETS
Не рекомендуется изменять данный раздел вручную - для редактирования данных параметров можно взаимодействовать с таблицей или используя встроенный диалог конфигурации таблицы.
* `current` - Название текущего выбранного набора настроек столбцов таблицы
* `<названиеабора>`
* `<название_столбца>` - значение представляет собой список (через ",") из булевого значения (отображается ли столбец в таблице) и целого числа больше 0 (ширину столбца в пикселах)
#### Раздел CHECKS
В этом разделе задаются параметры проверок коптера, которые регулируются на стороне сервера. Доступны следующие параметры:
@@ -239,6 +251,18 @@
* `host` - имя хоста или IP адрес NTP сервера (локального или удаленного)
* `port` - порт, используемый NTP сервером
#### Раздел TABLE
* `remove_disconnected` - Определяет поведение при разрыве связи с клиентом. При значении `True` вся информация о клиенте *будет удалена* как из внутренней памяти, так и *из таблицы*. *Это может привести к 'скачкам' таблицы при отключении клиентов.* При значении `False` отключённые клиенты *не будут* удалены из таблицы, но будут отображены с подсвечиванием ячейки в столбце `copter ID` красным цветом. Все данные будут сохранены. При переподключении клиента, он будет ассоциирован с той же строкой таблицы, а ячейка со значением `copter ID` вновь станет зелёного цвета.
##### Подраздел PRESETS
Не рекомендуется изменять данный раздел вручную - для редактирования данных параметров можно взаимодействовать с таблицей или используя встроенный диалог конфигурации таблицы.
* `current` - Название текущего выбранного набора настроек столбцов таблицы
* `<названиеабора>`
* `<название_столбца>` - значение представляет собой список (через ",") из булевого значения (отображается ли столбец в таблице) и целого числа больше 0 (ширину столбца в пикселах)
## Дополнительные операции и окна
### Visual land

43
drone/README.md Normal file
View File

@@ -0,0 +1,43 @@
# clever-show drone client
Application for remote synchronized control of drones and failsafe drone protection module.
* `client.py` is the main drone communication and control module
* `failsafe.py` is the drone protection module, which creates `/emergency_land` service and monitors the state of the drone in accordance with the logic specified in `[FAILSAFE]` item of `client.ini` settings file
## Requirements
* Ubuntu Bionic or Debian Stretch
* ROS Melodic
* Clover ROS package
* Python 2.7
* Time synchronization client
Can be used with built-in NTP client or with external package for time synchronization like `chrony` on Linux systems.
## Installation
```cmd
pip install -r requirements.txt
```
## Usage
Start roscore with clover package on the drone or on the PC with drone simulator.
Start client:
```cmd
python client.py
```
If you want to start failsafe module, execute in the other terminal:
```cmd
python failsafe.py
```
## Documentation
* English
* [Russian](../docs/ru/client.md)

View File

@@ -2,49 +2,62 @@ import os
import sys
import time
import math
import rospy
import numpy
import psutil
import logging
import datetime
import threading
import subprocess
from collections import namedtuple
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
# for backward compatibility with clever
# Import rospy
try:
import rospy
except ImportError:
print("Can't import rospy! Please check your ROS installation.")
exit()
import rospkg
# Import clever or clover package
try:
from clever import srv
except ImportError:
try:
from clover import srv
except ImportError:
print("Can't import clever or clover")
import datetime
import logging
import threading
import psutil
import subprocess
from collections import namedtuple
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
print("Can't import clever or clover! Please check installation of clover ROS package.")
exit()
# Import flight control
try:
from FlightLib import FlightLib
import modules.flight as flight
except ImportError:
print("Can't import FlightLib")
print("Can't import flight control module!")
# Import led control
try:
from FlightLib import LedLib
import modules.led as led
except ImportError:
print("Can't import LedLib")
print("Can't import led control module for Raspberry Pi!")
import client
# Add parent dir to PATH to import messaging_lib and config_lib
current_dir = (os.path.dirname(os.path.realpath(__file__)))
lib_dir = os.path.realpath(os.path.join(current_dir, '../lib'))
sys.path.insert(0, lib_dir)
import messaging_lib as messaging
import tasking_lib as tasking
import animation_lib as animation
from mavros_mavlink import *
import messaging
import modules.client_core as client_core
import modules.animation as animation
import modules.mavros_wrapper as mavros
import modules.tasking as tasking
from std_msgs.msg import Bool
from geometry_msgs.msg import Point, Quaternion, TransformStamped
from tf.transformations import quaternion_from_euler, euler_from_quaternion, quaternion_multiply
import tf2_ros
from geographiclib.geodesic import Geodesic
Earth = Geodesic.WGS84
@@ -85,41 +98,56 @@ logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logger.addHandler(handler)
animation_logger = logging.getLogger('animation_lib')
animation_logger = logging.getLogger('modules.animation')
animation_logger.setLevel(logging.INFO)
animation_logger.addHandler(handler)
client_logger = logging.getLogger('client')
client_logger = logging.getLogger('modules.client_core')
client_logger.setLevel(logging.DEBUG)
client_logger.addHandler(handler)
messaging_logger = logging.getLogger('messaging_lib')
messaging_logger = logging.getLogger('messaging')
messaging_logger.setLevel(logging.INFO)
messaging_logger.addHandler(handler)
tasking_logger = logging.getLogger('tasking_lib')
tasking_logger = logging.getLogger('modules.tasking')
tasking_logger.setLevel(logging.INFO)
tasking_logger.addHandler(handler)
flightlib_logger = logging.getLogger('FlightLib')
flightlib_logger.setLevel(logging.INFO)
flightlib_logger.addHandler(handler)
flight_logger = logging.getLogger('modules.flight')
flight_logger.setLevel(logging.INFO)
flight_logger.addHandler(handler)
mavros_mavlink_logger = logging.getLogger('mavros_mavlink')
mavros_mavlink_logger = logging.getLogger('modules.mavros_wrapper')
mavros_mavlink_logger.setLevel(logging.INFO)
mavros_mavlink_logger.addHandler(handler)
class CopterClient(client.Client):
class CopterClient(client_core.Client):
def __init__(self, config_path="config/client.ini"):
super(CopterClient, self).__init__(config_path)
self.load_config()
if self.config.clover_dir == 'auto':
self.check_clover_dir()
self.telemetry = None
self.animation = animation.Animation(self.config, "animation.csv")
def load_config(self):
super(CopterClient, self).load_config()
def check_clover_dir(self):
rospack = rospkg.RosPack()
try:
path = rospack.get_path('clever')
except rospkg.common.ResourceNotFound:
try:
path = rospack.get_path('clover')
except rospkg.common.ResourceNotFound:
path = 'error'
self.config.set('', 'clover_dir', path, write=True)
if path.count("/pi/"):
self.server_connection.whoami = "pi"
def on_broadcast_bind(self):
repair_chrony(self.config.server_host)
@@ -127,20 +155,18 @@ class CopterClient(client.Client):
rospy.loginfo("Init ROS node")
rospy.init_node('clever_show_client', anonymous=True)
if self.config.led_use:
from FlightLib import LedLib
LedLib.init_led(self.config.led_pin)
led.init(self.config.led_pin)
task_manager_instance.start()
start_subscriber()
mavros.start_subscriber()
self.telemetry = Telemetry()
self.telemetry.start_loop()
if self.config.copter_frame_id == "floor":
if self.config.flight_frame_id == "floor":
self.start_floor_frame_broadcast()
elif self.config.copter_frame_id == "gps":
elif self.config.flight_frame_id == "gps":
self.start_gps_frame_broadcast()
client_thread = threading.Thread(target=super(CopterClient, self).start, name="Client thread")
client_thread.daemon = True
client_thread.start()
#super(CopterClient, self).start()
def start_floor_frame_broadcast(self):
if self.config.floor_frame_parent == "gps":
@@ -153,7 +179,7 @@ class CopterClient(client.Client):
math.radians(self.config.floor_frame_rotation[1]),
math.radians(self.config.floor_frame_rotation[2])))
trans.header.frame_id = self.config.floor_frame_parent
trans.child_frame_id = self.config.copter_frame_id
trans.child_frame_id = self.config.flight_frame_id
static_broadcaster.sendTransform(trans)
def start_gps_frame_broadcast(self):
@@ -172,7 +198,7 @@ class CopterClient(client.Client):
def gps_frame_broadcast_loop(self):
rate = rospy.Rate(1)
while not rospy.is_shutdown():
telem = FlightLib.get_telemetry_locked(frame_id = "map")
telem = flight.get_telemetry_locked(frame_id = "map")
if telem is not None:
if math.isnan(telem.lat) or math.isnan(telem.lon) or math.isnan(telem.x) or math.isnan(telem.y):
logger.info("Can't get position from telemetry")
@@ -338,7 +364,7 @@ def _response_id(*args, **kwargs):
if new_id is not None:
old_id = copter.client_id
if new_id != old_id:
copter.config.set('PRIVATE', 'id', new_id, write=True)
copter.config.set('', 'id', new_id, write=True)
copter.client_id = new_id
if new_id != '/hostname':
if copter.config.system_restart_after_rename:
@@ -359,11 +385,11 @@ def _response_id(*args, **kwargs):
@messaging.request_callback("selfcheck")
def _response_selfcheck(*args, **kwargs):
if check_state_topic(wait_new_status=True):
check = FlightLib.selfcheck()
if mavros.check_state_topic(wait_new_status=True):
check = flight.selfcheck()
return check if check else "OK"
else:
stop_subscriber()
mavros.stop_subscriber()
return "NOT_CONNECTED_TO_FCU"
@@ -381,33 +407,33 @@ def _response_animation_id(*args, **kwargs):
@messaging.request_callback("batt_voltage")
def _response_batt(*args, **kwargs):
if check_state_topic(wait_new_status=True):
return FlightLib.get_telemetry_locked('body').voltage
if mavros.check_state_topic(wait_new_status=True):
return flight.get_telemetry_locked('body').voltage
else:
stop_subscriber()
mavros.stop_subscriber()
return float('nan')
@messaging.request_callback("cell_voltage")
def _response_cell(*args, **kwargs):
if check_state_topic(wait_new_status=True):
return FlightLib.get_telemetry_locked('body').cell_voltage
if mavros.check_state_topic(wait_new_status=True):
return flight.get_telemetry_locked('body').cell_voltage
else:
stop_subscriber()
mavros.stop_subscriber()
return float('nan')
@messaging.request_callback("sys_status")
def _response_sys_status(*args, **kwargs):
return get_sys_status()
return mavros.get_sys_status()
@messaging.request_callback("cal_status")
def _response_cal_status(*args, **kwargs):
if check_state_topic(wait_new_status=True):
return get_calibration_status()
if mavros.check_state_topic(wait_new_status=True):
return mavros.get_calibration_status()
else:
stop_subscriber()
mavros.stop_subscriber()
return "NOT_CONNECTED_TO_FCU"
@@ -415,24 +441,24 @@ def _response_cal_status(*args, **kwargs):
def _response_position(*args, **kwargs):
telem = copter.telemetry.ros_telemetry
return "{:.2f} {:.2f} {:.2f} {:.1f} {}".format(
telem.x, telem.y, telem.z, math.degrees(telem.yaw), copter.config.copter_frame_id)
telem.x, telem.y, telem.z, math.degrees(telem.yaw), copter.config.flight_frame_id)
@messaging.request_callback("calibrate_gyro")
def _calibrate_gyro(*args, **kwargs):
calibrate('gyro')
return get_calibration_status()
mavros.calibrate('gyro')
return mavros.get_calibration_status()
@messaging.request_callback("calibrate_level")
def _calibrate_level(*args, **kwargs):
calibrate('level')
return get_calibration_status()
mavros.calibrate('level')
return mavros.get_calibration_status()
@messaging.request_callback("load_params")
def _load_params(*args, **kwargs):
result = load_param_file('temp.params')
result = mavros.load_param_file('temp.params')
logger.info("Load parameters to FCU success: {}".format(result))
return result
@@ -451,7 +477,7 @@ def _command_update_animation(*args, **kwargs):
@messaging.message_callback("move_start")
def _command_move_start_to_current_position(*args, **kwargs):
offset = numpy.array(copter.config.private_offset) + numpy.array(copter.config.copter_common_offset)
offset = numpy.array(copter.config.animation_private_offset) + numpy.array(copter.config.animation_common_offset)
try:
xs, ys, zs = copter.animation.get_start_point(copter.config.ratio, offset)
except ValueError:
@@ -461,38 +487,36 @@ def _command_move_start_to_current_position(*args, **kwargs):
telem = copter.telemetry.ros_telemetry
logger.debug("telemetry x = {}, y = {}".format(telem.x, telem.y))
if valid([telem.x, telem.y, telem.z]):
copter.config.set('PRIVATE', 'offset',
[telem.x - xs, telem.y - ys, copter.config.private_offset[2]], write=True)
logger.info("Set start delta: {:.2f} {:.2f}".format(copter.config.private_offset[0],
copter.config.private_offset[1]))
copter.config.set('animation', 'private_offset',
[telem.x - xs, telem.y - ys, copter.config.animation_private_offset[2]], write=True)
logger.info("Set start delta: {:.2f} {:.2f}".format(copter.config.animation_private_offset[0],
copter.config.animation_private_offset[1]))
else:
logger.error("Wrong telemetry")
@messaging.message_callback("reset_start")
def _command_reset_start(*args, **kwargs):
copter.config.set('PRIVATE', 'offset',
[0, 0, copter.config.private_offset[2]], write=True)
logger.info("Reset start to {:.2f} {:.2f}".format(copter.config.private_offset[0],
copter.config.private_offset[1]))
copter.config.set('animation', 'private_offset', [0, 0, copter.config.animation_private_offset[2]], write=True)
logger.info("Reset start to {:.2f} {:.2f}".format(copter.config.animation_private_offset[0], copter.config.animation_private_offset[1]))
@messaging.message_callback("set_z_to_ground")
def _command_set_z(*args, **kwargs):
telem = copter.telemetry.ros_telemetry
if valid([telem.x, telem.y, telem.z]):
copter.config.set('PRIVATE', 'offset',
[copter.config.private_offset[0], copter.config.private_offset[1], telem.z], write=True)
logger.info("Set z offset to {:.2f}".format(copter.config.private_offset[2]))
copter.config.set('animation', 'private_offset',
[copter.config.animation_private_offset[0], copter.config.animation_private_offset[1], telem.z], write=True)
logger.info("Set z offset to {:.2f}".format(copter.config.animation_private_offset[2]))
else:
logger.error("Wrong telemetry")
@messaging.message_callback("reset_z_offset")
def _command_reset_z(*args, **kwargs):
copter.config.set('PRIVATE', 'offset',
[copter.config.private_offset[0], copter.config.private_offset[1], 0], write=True)
logger.info("Reset z offset to {:.2f}".format(copter.config.private_offset[2]))
copter.config.set('animation', 'private_offset',
[copter.config.animation_private_offset[0], copter.config.animation_private_offset[1], 0], write=True)
logger.info("Reset z offset to {:.2f}".format(copter.config.animation_private_offset[2]))
@messaging.message_callback("update_repo")
@@ -504,13 +528,13 @@ def _command_update_repo(*args, **kwargs):
@messaging.message_callback("reboot_all")
def _command_reboot_all(*args, **kwargs):
reboot_fcu()
mavros.reboot_fcu()
execute_command("reboot")
@messaging.message_callback("reboot_fcu")
def _command_reboot(*args, **kwargs):
reboot_fcu()
mavros.reboot_fcu()
@messaging.message_callback("service_restart")
@@ -530,9 +554,9 @@ def _command_chrony_repair(*args, **kwargs):
@messaging.message_callback("led_test")
def _command_led_test(*args, **kwargs):
LedLib.chase(255, 255, 255)
led.chase(255, 255, 255)
time.sleep(2)
LedLib.off()
led.off()
@messaging.message_callback("led_fill")
@@ -540,12 +564,12 @@ def _command_led_fill(*args, **kwargs):
r = kwargs.get("red", 0)
g = kwargs.get("green", 0)
b = kwargs.get("blue", 0)
LedLib.fill(r, g, b)
led.fill(r, g, b)
@messaging.message_callback("flip")
def _copter_flip(*args, **kwargs):
FlightLib.flip(frame_id=copter.config.copter_frame_id)
flight.flip(frame_id=copter.config.flight_frame_id)
@messaging.message_callback("takeoff")
@@ -553,8 +577,8 @@ def _command_takeoff(*args, **kwargs):
logger.info("Takeoff at {}".format(datetime.datetime.now()))
task_manager.add_task(0, 0, animation.takeoff,
task_kwargs={
"z": copter.config.copter_takeoff_height,
"timeout": copter.config.copter_takeoff_time,
"z": copter.config.flight_takeoff_height,
"timeout": copter.config.flight_takeoff_time,
"safe_takeoff": False,
"use_leds": copter.config.led_use & copter.config.led_takeoff_indication,
})
@@ -569,16 +593,16 @@ def _command_takeoff_z(*args, **kwargs):
except ValueError:
logger.error("takeoff_z: Wrong z argument!")
else:
telem = FlightLib.get_telemetry_locked(copter.config.copter_frame_id)
telem = flight.get_telemetry_locked(copter.config.flight_frame_id)
if valid([telem.x, telem.y, telem.z]):
logger.info("Takeoff to z = {} at {}".format(z, datetime.datetime.now()))
task_manager.add_task(0, 0, FlightLib.reach_point,
task_manager.add_task(0, 0, flight.reach_point,
task_kwargs={
"x": telem.x,
"y": telem.y,
"z": z,
"frame_id": copter.config.copter_frame_id,
"timeout": copter.config.copter_takeoff_time,
"frame_id": copter.config.flight_frame_id,
"timeout": copter.config.flight_takeoff_time,
"auto_arm": True,
})
else:
@@ -590,22 +614,26 @@ def _command_land(*args, **kwargs):
task_manager.reset()
task_manager.add_task(0, 0, animation.land,
task_kwargs={
"z": copter.config.copter_takeoff_height,
"timeout": copter.config.copter_land_timeout,
"frame_id": copter.config.copter_frame_id,
"z": copter.config.flight_takeoff_height,
"timeout": copter.config.flight_land_timeout,
"frame_id": copter.config.flight_frame_id,
"use_leds": copter.config.led_use & copter.config.led_land_indication,
})
@messaging.message_callback("emergency_land")
def _emergency_land(*args, **kwargs):
logger.info(FlightLib.emergency_land().message)
try:
result = flight.emergency_land()
logger.info(result.message)
except rospy.ServiceException:
logger.error("Can't execute emergency land: service is unavailable!")
@messaging.message_callback("disarm")
def _command_disarm(*args, **kwargs):
task_manager.reset()
task_manager.add_task(-5, 0, FlightLib.arming_wrapper,
task_manager.add_task(-5, 0, flight.arming_wrapper,
task_kwargs={
"state": False
})
@@ -645,7 +673,7 @@ def _play_animation(*args, **kwargs):
return
# Get output frames
offset = numpy.array(copter.config.private_offset) + numpy.array(copter.config.copter_common_offset)
offset = numpy.array(copter.config.animation_private_offset) + numpy.array(copter.config.animation_common_offset)
frames = copter.animation.get_scaled_output(copter.config.animation_ratio, offset)
if not frames:
logger.error("start: No frames in animation!")
@@ -672,22 +700,22 @@ def _play_animation(*args, **kwargs):
takeoff_time = start_time + start_delay
task_manager.add_task(takeoff_time, 0, animation.takeoff,
task_kwargs={
"z": copter.config.copter_takeoff_height,
"timeout": copter.config.copter_takeoff_time,
"z": copter.config.flight_takeoff_height,
"timeout": copter.config.flight_takeoff_time,
"safe_takeoff": False,
"use_leds": copter.config.led_use & copter.config.led_takeoff_indication,
})
# Fly to first point
rfp_time = takeoff_time + copter.config.copter_takeoff_time
rfp_time = takeoff_time + copter.config.flight_takeoff_time
task_manager.add_task(rfp_time, 0, animation.execute_frame,
task_kwargs={
"frame": frames[0],
"frame_id": copter.config.copter_frame_id,
"frame_id": copter.config.flight_frame_id,
"use_leds": copter.config.led_use,
"flight_func": FlightLib.reach_point,
"flight_func": flight.reach_point,
})
# Calculate first frame start time
frame_time = rfp_time + copter.config.copter_reach_first_point_time
frame_time = rfp_time + copter.config.flight_reach_first_point_time
elif start_action == 'fly':
# Calculate start time
@@ -695,31 +723,31 @@ def _play_animation(*args, **kwargs):
task_manager.add_task(arm_time, 0, animation.execute_frame,
task_kwargs={
"frame": frames[0],
"frame_id": copter.config.copter_frame_id,
"frame_id": copter.config.flight_frame_id,
"use_leds": copter.config.led_use,
"flight_func": FlightLib.navto,
"flight_func": flight.navto,
"auto_arm": True,
})
# Calculate first frame start time
frame_time = arm_time + copter.config.copter_arming_time
frame_time = arm_time + copter.config.flight_arming_time
logger.debug("Start queue {}".format(task_manager.task_queue))
# Play animation file
for frame in frames:
task_manager.add_task(frame_time, 0, animation.execute_frame,
task_kwargs={
"frame": frame,
"frame_id": copter.config.copter_frame_id,
"frame_id": copter.config.flight_frame_id,
"use_leds": copter.config.led_use,
"flight_func": FlightLib.navto,
"flight_func": flight.navto,
})
frame_time += frame.delay
# Calculate land_time
land_time = frame_time + copter.config.copter_land_delay
land_time = frame_time + copter.config.flight_land_delay
# Land
task_manager.add_task(land_time, 0, animation.land,
task_kwargs={
"timeout": copter.config.copter_land_timeout,
"frame_id": copter.config.copter_frame_id,
"timeout": copter.config.flight_land_timeout,
"frame_id": copter.config.flight_frame_id,
"use_leds": copter.config.led_use & copter.config.led_land_indication,
})
@@ -776,7 +804,7 @@ class Telemetry:
return "{} V{}".format(copter.config.config_name, copter.config.config_version)
def get_start_position(self):
offset = numpy.array(copter.config.private_offset) + numpy.array(copter.config.copter_common_offset)
offset = numpy.array(copter.config.animation_private_offset) + numpy.array(copter.config.animation_common_offset)
try:
x, y, z = copter.animation.get_start_point(copter.config.animation_ratio, offset)
except ValueError:
@@ -790,7 +818,7 @@ class Telemetry:
start_action = copter.animation.get_start_action(
copter.config.animation_start_action, self.ros_telemetry.z,
copter.config.animation_takeoff_level, copter.config.animation_ground_level,
copter.config.animation_ratio, offset)
copter.config.animation_ratio, offset, self.fcu_status)
return [x,y,z,yaw,start_action,start_delay]
@classmethod
@@ -800,9 +828,9 @@ class Telemetry:
battery_v = ros_telemetry.voltage
batt_empty_param = get_param('BAT_V_EMPTY')
batt_charged_param = get_param('BAT_V_CHARGED')
batt_cells_param = get_param('BAT_N_CELLS')
batt_empty_param = mavros.get_param('BAT_V_EMPTY')
batt_charged_param = mavros.get_param('BAT_V_CHARGED')
batt_cells_param = mavros.get_param('BAT_N_CELLS')
if batt_empty_param.success and batt_charged_param.success and batt_cells_param.success:
batt_empty = batt_empty_param.value.real
@@ -818,7 +846,7 @@ class Telemetry:
@classmethod
def get_selfcheck(cls):
check = FlightLib.selfcheck()
check = flight.selfcheck()
if not check:
check = "OK"
return check
@@ -830,7 +858,7 @@ class Telemetry:
except AttributeError:
return 'NO_POS'
if not math.isnan(x):
return x, y, z, math.degrees(ros_telemetry.yaw), copter.config.copter_frame_id
return x, y, z, math.degrees(ros_telemetry.yaw), copter.config.flight_frame_id
return 'NO_POS'
def get_ros_telemetry(self):
@@ -839,7 +867,7 @@ class Telemetry:
def update_telemetry_fast(self):
self.last_task = task_manager.get_current_task()
try:
self.ros_telemetry = FlightLib.get_telemetry_locked(copter.config.copter_frame_id)
self.ros_telemetry = flight.get_telemetry_locked(copter.config.flight_frame_id)
if self.ros_telemetry.connected:
self.armed = self.ros_telemetry.armed
self.mode = self.ros_telemetry.mode
@@ -863,8 +891,8 @@ class Telemetry:
self.config_version = self.get_config_version()
self.start_position = self.get_start_position()
try:
self.calibration_status = get_calibration_status()
self.fcu_status = get_sys_status()
self.calibration_status = mavros.get_calibration_status()
self.fcu_status = mavros.get_sys_status()
self.battery = self.get_battery(self.ros_telemetry)
except rospy.ServiceException:
rospy.logdebug("Some service is unavailable")
@@ -919,7 +947,7 @@ class Telemetry:
logger.info("Clear task manager because of {}".format(log_msg))
logger.info("Mode: {} | armed: {} | last task: {} ".format(mode, armed, last_task))
task_manager.reset()
FlightLib.reset_delta()
flight.reset_delta()
self._tasks_cleared = True
self._interruption_counter = 0
else:

View File

@@ -1,5 +1,8 @@
config_name = string(default='client')
config_version = float(default=1.0)
# Available options: /hostname ; /default ; /ip ; any string 63 characters length
id = string(default=/hostname, max=63)
clover_dir = string(default=auto)
[SERVER]
port = integer(default=25000, min=1)
@@ -15,9 +18,78 @@ transmit = boolean(default=True)
frequency = float(default=1.0, min=0)
log_resources = boolean(default=False)
[POSITION WATCHDOG]
enabled = boolean(default=True)
log_state = boolean(default=True)
[FLIGHT]
frame_id = string(default=map)
arming_time = float(default=0.5)
takeoff_height = float(default=1.0)
takeoff_time = float(default=5.0, min=0)
reach_first_point_time = float(default=5.0, min=0)
land_delay = float(default=1.0, min=0)
land_timeout = float(default=10.0, min=0)
[FLOOR FRAME]
# Available options: map, aruco_map, gps, floor
parent = string(default=map)
# Frame translation (x, y, z)
# __list__ x y z
translation = float_list(default=list(0.0, 0.0, 0.0), min=3, max=3)
# Frame rotation (roll, pitch, yaw) in degrees
# __list__ roll pitch yaw
rotation = float_list(default=list(0.0, 0.0, 0.0), min=3, max=3)
[GPS FRAME]
lat = string(default=0)
lon = string(default=0)
yaw = float(default=0)
[ANIMATION]
# Available options:
# 'auto' - automatic action selection from 'takeoff' or 'fly' based on current copter level
# if (start animation point z) - (current height of copter) > (takeoff level) then
# start_action is set to 'takeoff', else start_action is set to 'fly'
# 'takeoff' - takeoff to first output animation point after static_begin_time then execute 'takeoff logic'
# 'fly' - execute animation frames after static_begin_time
start_action = string(default=auto)
# Takeoff level to set start action automatically
takeoff_level = float(default=0.5)
# Available options:
# * 'current' - current height of copter
# * z coordinate in meters
ground_level = string(default=current)
# Frame delay in seconds
frame_delay = float(default=0.1, min=0.01)
# Available options:
# * 'animation' - take yaw from animation
# * 'nan' - don't change yaw during flight
# * a number in degrees
yaw = string(default=nan)
# Animation ratio (x, y, z)
# __list__ x y z
ratio = float_list(default=list(1.0, 1.0, 1.0), min=3, max=3)
# Drone's animation individual offset (x, y, z)
# __list__ x y z
private_offset = float_list(default=list(0, 0, 0), min=3, max=3)
# Drone's animation common offset
# __list__ x y z
common_offset = float_list(default=list(0, 0, 0), min=3, max=3)
# Flags for output frames
[[OUTPUT]]
static_begin = boolean(default=False)
takeoff = boolean(default=True)
route = boolean(default=True)
land = boolean(default=False)
static_end = boolean(default=False)
[LED]
use = boolean(default=False)
pin = integer(default=21, min=0, max=100)
count = integer(default=60, min=1)
takeoff_indication = boolean(default=True)
land_indication = boolean(default=True)
[FAILSAFE]
enabled = boolean(default=False)
log_state = boolean(default=False)
# Available options: emergency_land, land, disarm
action = string(default=emergency_land)
# Time to get vision position after arm
@@ -37,72 +109,9 @@ disarm_timeout = float(default=10.0, min=0)
thrust = float(default=0.45, min=0, max=1)
decrease_thrust_after = float(default=5.0, min=0)
[COPTER]
frame_id = string(default=map)
arming_time = float(default=0.5)
takeoff_height = float(default=1.0)
takeoff_time = float(default=5.0, min=0)
reach_first_point_time = float(default=5.0, min=0)
land_delay = float(default=1.0, min=0)
land_timeout = float(default=10.0, min=0)
# __list__ x y z
common_offset = float_list(default=list(0, 0, 0), min=3, max=3)
[FLOOR FRAME]
parent = string(default=map)
# Frame translation (x, y, z)
# __list__ x y z
translation = float_list(default=list(0.0, 0.0, 0.0), min=3, max=3)
# Frame rotation (roll, pitch, yaw) in degrees
# __list__ roll pitch yaw
rotation = float_list(default=list(0.0, 0.0, 0.0), min=3, max=3)
[GPS FRAME]
lat = string(default=0)
lon = string(default=0)
yaw = float(default=0)
[ANIMATION]
# Available options:
# 'auto' - automatic action selection from 'takeoff' or 'fly' based on current copter level
# 'takeoff' - takeoff to first output animation point after static_begin_time then execute 'takeoff logic'
# 'fly' - execute animation frames after static_begin_time
start_action = string(default=auto)
takeoff_level = float(default=0.5)
ground_level = float(default=0)
frame_delay = float(default=0.1, min=0.01)
# Animation ratio (x, y, z)
# __list__ x y z
ratio = float_list(default=list(1.0, 1.0, 1.0), min=3, max=3)
# Available options:
# 'animation' - take yaw from animation
# 'nan' - don't change yaw during flight
# or a number in degrees
yaw = string(default=180.0)
[[OUTPUT]]
static_begin = boolean(default=False)
takeoff = boolean(default=True)
route = boolean(default=True)
land = boolean(default=False)
static_end = boolean(default=False)
[LED]
use = boolean(default=False)
pin = integer(default=21, min=0, max=100)
count = integer(default=60, min=1)
takeoff_indication = boolean(default=True)
land_indication = boolean(default=True)
[PRIVATE]
# Available options: /hostname ; /default ; /ip ; any string 63 characters length
id = string(default=/hostname, max=63) #TODO our re check
# Drone's individual offset (x, y, z)
# __list__ x y z
offset = float_list(default=list(0, 0, 0), min=3, max=3)
[SYSTEM]
change_hostname = boolean(default=True)
restart_after_rename = boolean(default=True)
change_hostname = boolean(default=False)
restart_after_rename = boolean(default=False)
[NTP]
use = boolean(default=False)

View File

@@ -8,9 +8,9 @@ import threading
# for backward compatibility with clever
try:
from clever import SetAttitude
from clever import srv
except ImportError:
from clover import SetAttitude
from clover import srv
from sensor_msgs.msg import Range
from mavros_msgs.msg import State, PositionTarget
@@ -19,23 +19,23 @@ from std_msgs.msg import Bool
from std_srvs.srv import Trigger, TriggerResponse
from geometry_msgs.msg import PoseStamped
import 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)
# Add parent dir to PATH to import messaging_lib and config_lib
current_dir = (os.path.dirname(os.path.realpath(__file__)))
lib_dir = os.path.realpath(os.path.join(current_dir, '../lib'))
sys.path.insert(0, lib_dir)
from config import ConfigManager
config = ConfigManager()
config.load_config_and_spec("config/client.ini")
watchdog_is_enabled = config.position_watchdog_enabled
log_state = config.position_watchdog_log_state
vision_pose_delay_after_arm = config.position_watchdog_vision_pose_delay_after_arm
visual_pose_timeout = config.position_watchdog_vision_pose_timeout
pos_delta_max = config.position_watchdog_position_delta_max
watchdog_action = config.position_watchdog_action
timeout_to_disarm = config.position_watchdog_disarm_timeout
watchdog_is_enabled = config.failsafe_enabled
log_state = config.failsafe_log_state
vision_pose_delay_after_arm = config.failsafe_vision_pose_delay_after_arm
visual_pose_timeout = config.failsafe_vision_pose_timeout
pos_delta_max = config.failsafe_position_delta_max
watchdog_action = config.failsafe_action
timeout_to_disarm = config.failsafe_disarm_timeout
emergency_land_thrust = config.emergency_land_thrust
emergency_land_decrease_thrust_after = config.emergency_land_decrease_thrust_after
@@ -58,7 +58,7 @@ logger.addHandler(handler)
set_mode = rospy.ServiceProxy('/mavros/set_mode', SetMode)
arming = rospy.ServiceProxy('/mavros/cmd/arming', CommandBool)
set_attitude = rospy.ServiceProxy('/set_attitude', SetAttitude)
set_attitude = rospy.ServiceProxy('/set_attitude', srv.SetAttitude)
visual_pose_last_timestamp = 0
armed = False

View File

@@ -8,17 +8,20 @@ import rospy
import logging
import threading
try:
from FlightLib import FlightLib
except ImportError:
print("Can't import FlightLib")
try:
from FlightLib import LedLib
except ImportError:
print("Can't import LedLib")
logger = logging.getLogger(__name__)
# Import flight control
try:
import modules.flight as flight
except ImportError:
logger.debug("Can't import flight control module!")
# Import led control
try:
import modules.led as led
except ImportError:
logger.debug("Can't import led control module for Raspberry Pi!")
interrupt_event = threading.Event()
def moving(f1, f2, delta, x=True, y=True, z=True):
@@ -319,15 +322,23 @@ class Animation(object):
return ground_level <= self.get_scaled_output_min_z(ratio, offset)
def get_start_action(self, start_action, current_height, takeoff_level,
ground_level, ratio=(1,1,1), offset=(0,0,0)):
ground_level, ratio=(1,1,1), offset=(0,0,0), state="STANDBY"):
# Check output frames
if not self.output_frames:
return 'error: empty output frames'
if math.isnan(current_height):
return 'error: bad current_height'
return 'error: bad copter height'
# Check that bottom point of animation is higher than ground level
if ground_level > self.get_scaled_output_min_z(ratio, offset):
return 'error: some animation points are lower than ground level'
if ground_level == 'current':
ground_level = current_height
try:
ground_level = float(ground_level)
except ValueError:
return 'error in [ANIMATION] ground_level parameter'
if state != "ACTIVE" and ground_level > self.get_scaled_output_min_z(ratio, offset):
return 'error: some animation points are lower than ground level for {:.2f}m (max)'.format(
ground_level - self.get_scaled_output_min_z(ratio, offset)
)
# Select start action
if start_action == 'auto':
if self.get_start_point(ratio, offset)[2] - current_height > takeoff_level:
@@ -337,7 +348,7 @@ class Animation(object):
elif start_action in ('takeoff', 'fly'):
return start_action
else:
return 'error'
return 'error in [ANIMATION] start_action parameter'
# Need for tests
def save_corrected_animation(self):
@@ -350,7 +361,7 @@ class Animation(object):
try:
def execute_frame(frame, frame_id='aruco_map', use_leds=True,
flight_func=FlightLib.navto, auto_arm=False, flight_kwargs=None, interrupter=interrupt_event):
flight_func=flight.navto, auto_arm=False, flight_kwargs=None, interrupter=interrupt_event):
if flight_kwargs is None:
flight_kwargs = {}
if frame.pose_is_valid():
@@ -359,27 +370,27 @@ try:
logger.debug("Frame pose is not valid for flying")
if use_leds:
if frame.get_color:
LedLib.fill(*color)
led.fill(*color)
def takeoff(z=1.5, safe_takeoff=True, frame_id='map', timeout=5.0, use_leds=True,
interrupter=interrupt_event):
if use_leds:
LedLib.wipe_to(255, 0, 0, interrupter=interrupter)
result = FlightLib.takeoff(height=z, timeout_takeoff=timeout, frame_id=frame_id,
led.wipe_to(255, 0, 0, interrupter=interrupter)
result = flight.takeoff(height=z, timeout_takeoff=timeout, frame_id=frame_id,
emergency_land=safe_takeoff, interrupter=interrupter)
if result == 'not armed' or result == 'timeout':
raise Exception('STOP') # Raise exception to clear task_manager if copter can't arm
if use_leds:
LedLib.blink(0, 255, 0, wait=50, interrupter=interrupter)
led.blink(0, 255, 0, wait=50, interrupter=interrupter)
def land(z=1.5, descend=False, timeout=5.0, frame_id='aruco_map', use_leds=True,
interrupter=interrupt_event):
if use_leds:
LedLib.blink(255, 0, 0, interrupter=interrupter)
FlightLib.land(z=z, descend=descend, timeout_land=timeout, frame_id_land=frame_id, interrupter=interrupter)
led.blink(255, 0, 0, interrupter=interrupter)
flight.land(z=z, descend=descend, timeout_land=timeout, frame_id_land=frame_id, interrupter=interrupter)
if use_leds:
LedLib.off()
led.off()
except NameError:
print("Can't create flying functions")

View File

@@ -10,14 +10,14 @@ import selectors2 as selectors
from contextlib import closing
import 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)
# Add parent dir to PATH to import messaging_lib and config_lib
current_dir = (os.path.dirname(os.path.realpath(__file__)))
lib_dir = os.path.realpath(os.path.join(current_dir, '../../lib'))
sys.path.insert(0, lib_dir)
logger = logging.getLogger(__name__)
import messaging_lib as messaging
import messaging
from config import ConfigManager
active_client = None # needs to be refactored: Singleton \ factory callbacks
@@ -28,7 +28,7 @@ class Client(object):
self.selector = selectors.DefaultSelector()
self.client_socket = None
self.server_connection = messaging.ConnectionManager("pi")
self.server_connection = messaging.ConnectionManager()
self.connected = False
self.client_id = None
@@ -43,10 +43,10 @@ class Client(object):
def load_config(self):
self.config.load_config_and_spec(self.config_path)
config_id = self.config.private_id.lower()
config_id = self.config.id.lower()
if config_id == '/default':
self.client_id = 'copter' + str(random.randrange(9999)).zfill(4)
self.config.set('PRIVATE', 'id', self.client_id, write=True) # set and write
self.config.set('', 'id', self.client_id, write=True) # set and write
elif config_id == '/hostname':
self.client_id = socket.gethostname()
elif config_id == '/ip':
@@ -223,6 +223,10 @@ def _response_config(*args, **kwargs):
response.update({"configspec": dict(active_client.config.config.configspec)})
return response
@messaging.request_callback("clover_dir")
def _response_clover_dir(*args, **kwargs):
return active_client.config.clover_dir
@messaging.request_callback("id")
def _response_id(*args, **kwargs):
new_id = kwargs.get("new_id", None)

View File

@@ -29,7 +29,7 @@ arming = rospy.ServiceProxy('/mavros/cmd/arming', CommandBool)
landing = rospy.ServiceProxy('/land', Trigger)
emergency_land = rospy.ServiceProxy('/emergency_land', Trigger)
services_list = ['/navigate', '/set_position', '/set_rates', '/mavros/set_mode',
services_list = ['/navigate', '/set_position', '/set_rates', '/mavros/set_mode',
'/get_telemetry', '/mavros/cmd/arming', '/land', '/mavros/param/get']
logger.info("Proxy services inited")

View File

@@ -205,7 +205,7 @@ def strip_off():
def led_thread():
global mode
logger.info("Starting LedLib thread")
logger.info("Starting led control thread")
iteration = 0
while True:
if mode == "rainbow":
@@ -242,7 +242,7 @@ def led_thread():
# init
def init_led(led_pin = LED_PIN):
def init(led_pin = LED_PIN):
global strip
strip = Adafruit_NeoPixel(LED_COUNT, led_pin, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL)
strip.begin()
@@ -252,7 +252,7 @@ def init_led(led_pin = LED_PIN):
if __name__ == '__main__':
init_led()
init()
try:
rainbow()
except KeyboardInterrupt:

View File

@@ -136,7 +136,7 @@ def load_param_file(px4_file):
try:
px4_params = open(px4_file)
except IOError:
logger.error("File {} can't be opened".format(filepath))
logger.error("File {} can't be opened".format(px4_file))
result = False
else:
with open(px4_file) as px4_params:

View File

@@ -0,0 +1,4 @@
server 127.0.0.1 iburst
driftfile /var/lib/chrony/drift
makestep 1.0 -1
rtcsync

View File

@@ -8,4 +8,3 @@ allow 192.168.0.0/16
makestep 1.0 3
smoothtime 50000 0.01
rtcsync

View File

@@ -0,0 +1,113 @@
# This is generated config with default values
# Modify to configure
config_name = client
config_version = 1.0
[SERVER]
port = 25000
host = 192.168.1.54
buffer_size = 1024
[BROADCAST]
use = True
port = 8181
[TELEMETRY]
transmit = True
frequency = 1.0
log_resources = False
[POSITION WATCHDOG]
enabled = True
log_state = True
# Available options: emergency_land, land, disarm
action = emergency_land
# Time to get vision position after arm
# No visual position will be checked
# during this time after arming
vision_pose_delay_after_arm = 3.0
# Timeout for the last vision pose in /mavros/vision_pose/pose
# Set 0 to disable vision pose check
vision_pose_timeout = 0.0
# Max delta between current position and setpoint
# Set 0 to disable position delta check
position_delta_max = 3.0
# Time to disarm after action is triggered
disarm_timeout = 10.0
[EMERGENCY LAND]
thrust = 0.45
decrease_thrust_after = 5.0
[COPTER]
frame_id = floor
arming_time = 0.5
takeoff_height = 3.0
takeoff_time = 10.0
reach_first_point_time = 10.0
land_delay = 1.0
land_timeout = 10.0
# __list__ x y z
common_offset = 0.0, 0.0, 0.0
[FLOOR FRAME]
parent = gps
# Frame translation (x, y, z)
# __list__ x y z
translation = 0.0, 0.0, 5.0
# Frame rotation (roll, pitch, yaw) in degrees
# __list__ roll pitch yaw
rotation = 0.0, 0.0, 0.0
[GPS FRAME]
lat = 55.7032026
lon = 37.7248114
yaw = 0.0
[ANIMATION]
# Available options:
# 'auto' - automatic action selection from 'takeoff' or 'fly' based on current copter level
# 'takeoff' - takeoff to first output animation point after static_begin_time then execute 'takeoff logic'
# 'fly' - execute animation frames after static_begin_time
start_action = auto
takeoff_level = 0.5
ground_level = -5.0
frame_delay = 0.1
# Animation ratio (x, y, z)
# __list__ x y z
ratio = 0.5, 0.5, 0.5
# Available options:
# 'animation' - take yaw from animation
# 'nan' - don't change yaw during flight
# or a number in degrees
yaw = nan
[[OUTPUT]]
static_begin = False
takeoff = True
route = True
land = False
static_end = False
[LED]
use = False
pin = 21
count = 60
takeoff_indication = True
land_indication = True
[PRIVATE]
# Available options: /hostname ; /default ; /ip ; any string 63 characters length
id = /hostname
# Drone's individual offset (x, y, z)
# __list__ x y z
offset = 0.0, 0.0, 0.0
[SYSTEM]
change_hostname = True
restart_after_rename = True
[NTP]
use = False
host = ntp1.stratum2.ru
port = 123

View File

@@ -60,7 +60,10 @@ class ConfigManager:
return self.config[section][option]
def set(self, section, option, value, write=False):
self.config[section][option] = value
if section:
self.config[section][option] = value
else:
self.config[option] = value
if write:
self.write()
@@ -311,15 +314,15 @@ class ConfigManager:
if __name__ == '__main__':
cfg = ConfigManager()
cfg.load_from_file('Drone/config/client.ini')
# cfg.load_from_file('Server/config/server.ini')
#cfg.load_from_file('Drone/config/spec/configspec_client.ini')
cfg.load_from_file('drone/config/client.ini')
# cfg.load_from_file('server/config/server.ini')
#cfg.load_from_file('drone/config/spec/configspec_client.ini')
print(dict(cfg.full_dict(include_defaults=True)))
cfg.config.pop("PRIVATE", None)
print(cfg.config)
# cfg.load_config_and_spec('Drone/config/client.ini')
# cfg.load_config_and_spec('drone/config/client.ini')
# #print(cfg.config.comments)
# #print(cfg.server_host)
# cfg.server_host = '192.168.1.103'
@@ -338,7 +341,7 @@ if __name__ == '__main__':
import pprint
#pprint.pprint(cfg.full_dict)
# cfg2 = ConfigManager()
# #cfg2.load_from_dict({"PRIVATE": {"offset": [1, 2, 3]}}, configspec='Drone/config/spec/configspec_client.ini')
# #cfg2.load_from_dict({"PRIVATE": {"offset": [1, 2, 3]}}, configspec='drone/config/spec/configspec_client.ini')
# cfg2.load_from_dict({"PRIVATE": {"id": "heh"}})
# #pprint.pprint(cfg2.full_dict)
# #cfg.merge(cfg2)
@@ -350,7 +353,7 @@ if __name__ == '__main__':
# #print(cfg.full_dict)
#
# #cfg.load_from_dict(cfg.full_dict, 'Drone/config/client.ini')
# #cfg.load_from_dict(cfg.full_dict, 'drone/config/client.ini')
# #print(cfg.config.initial_comment, cfg.config.final_comment)
# #cfg.write()
#

View File

27
server/README.md Normal file
View File

@@ -0,0 +1,27 @@
# clever-show server
Application for creating and running drone shows, adjusting drones, animations and music.
## Requirements
* python 3.6+
* Time synchronization with connected drones.
Can be used with built-in NTP server or with external package for time synchronization like `chrony` on Linux systems.
## Installation
```cmd
pip3 install -r requirements.txt
```
## Usage
```cmd
python3 server.py
```
## Documentation
* English
* [Russian](../docs/ru/server.md)

View File

@@ -5,8 +5,28 @@ config_version = float(default='1.0')
port = integer(default=25000)
buffer_size = integer(default=1024)
[CLIENT]
clever_dir = string(default=/home/pi/catkin_ws/src/clever/clover)
[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=3.0, min=0)
[NTP]
use = boolean(default=False)
host = string(default=ntp1.stratum2.ru)
port = integer(default=123)
[TABLE]
# True -> clients are removed on disconnection
@@ -30,26 +50,3 @@ config_version = float(default='1.0')
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)

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
server/icons/test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -1,3 +1,5 @@
import os
import sys
import pickle
import logging
from ast import literal_eval
@@ -10,14 +12,12 @@ from PyQt5.QtGui import QCursor, QKeySequence
from PyQt5.QtWidgets import QAbstractItemView, QTreeView, QMenu, QAction, QMessageBox, QInputDialog, QFileDialog, \
QShortcut
import config_editor
import modules.ui.config_editor as 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)
# Add parent dir to PATH to import messaging_lib and config_lib
current_dir = (os.path.dirname(os.path.realpath(__file__)))
lib_dir = os.path.realpath(os.path.join(current_dir, '../../lib'))
sys.path.insert(0, lib_dir)
import config

View File

@@ -8,8 +8,8 @@ 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
from modules.config_editor_models import ConfigDialog
import modules.copter_table_models as table
def save_preset(config, current, header_dict):

View File

@@ -10,13 +10,13 @@ import selectors
import collections
import traceback
import inspect # Add parent dir to PATH to import messaging_lib and config_lib
# Add parent dir to PATH to import messaging_lib and config_lib
current_dir = (os.path.dirname(os.path.realpath(__file__)))
lib_dir = os.path.realpath(os.path.join(current_dir, '../../lib'))
sys.path.insert(0, lib_dir)
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 messaging_lib as messaging
# Import modules from lib dir
import messaging
from config import ConfigManager
random.seed()
@@ -38,7 +38,7 @@ ConfigOption = collections.namedtuple("ConfigOption", ["section", "option", "val
class Server(messaging.Singleton):
def __init__(self, server_id=None, config_path="config/server.ini"):
def __init__(self, config_path="../config/server.ini", server_id=None):
self.id = server_id if server_id else str(random.randint(0, 9999)).zfill(4)
self.time_started = 0
@@ -286,6 +286,7 @@ class Client(messaging.ConnectionManager):
def __init__(self, ip):
super().__init__()
self.copter_id = None
self.clover_dir = None
self.connected = False
self.clients[ip] = self
@@ -316,9 +317,15 @@ class Client(messaging.ConnectionManager):
old_id = self.copter_id
self.copter_id = value
if old_id is None:
self.get_response("clover_dir", self._got_clover_dir)
if old_id is None and self.on_first_connect:
self.on_first_connect(self)
def _got_clover_dir(self, _client, value):
self.clover_dir = value
def close(self, inner=False):
self.connected = False

View File

@@ -2,7 +2,7 @@ from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QKeySequence
from PyQt5 import QtWidgets
import visual_land
import modules.ui.visual_land as visual_land
import math
import logging
import sys

View File

@@ -8,30 +8,33 @@ import asyncio
import platform
import itertools
import subprocess
from functools import partial, wraps
from functools import partial, wraps
from quamash import QEventLoop
# Import server routines
from modules.server_core import Server, Client, now
# Import modules from lib, that was added to PATH on the previous step
import messaging
import config as cfg
from lib import b_partial
# Import PyQt5 related functions
from PyQt5 import QtWidgets, QtMultimedia, QtCore
from PyQt5.QtGui import QPixmap, QIcon
from PyQt5.QtCore import Qt, pyqtSlot, QUrl
from PyQt5.QtWidgets import QFileDialog, QMessageBox, QApplication, QInputDialog, QLineEdit, QStatusBar, \
QSplashScreen, QProgressBar
from quamash import QEventLoop
# Importing gui form
from server_gui import Ui_MainWindow
# Import gui form
from modules.ui.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, HeaderEditDialog
from visual_land_dialog import VisualLandDialog
from config_editor_models import ConfigDialog
from lib import b_partial
# Import gui logic
import modules.copter_table_models as table
from modules.copter_table import CopterTableWidget, HeaderEditDialog
from modules.visual_land_dialog import VisualLandDialog
from modules.config_editor_models import ConfigDialog
startup_cwd = os.getcwd()
@@ -378,7 +381,7 @@ class MainWindow(QtWidgets.QMainWindow):
data = str(value)
self.model.update_data(row, col, data, table.ModelDataRole)
def _send_files(self, files, copters=None, client_path="", client_filename="", match_id=False, callback=None):
def _send_files(self, files, copters=None, client_path="", client_filename="", match_id=False, callback=None, clover_dir=False):
if copters is None:
copters = self.model.user_selected()
copters = list(copters)
@@ -401,12 +404,19 @@ class MainWindow(QtWidgets.QMainWindow):
filename = client_filename.format(num, filename) or filename
for copter in to_send:
copter.client.send_file(file, os.path.join(client_path, filename))
if clover_dir:
if copter.client.clover_dir != 'error':
path_to_send = os.path.realpath(os.path.join(copter.client.clover_dir, client_path))
else:
logging.error("Can't send files to clover ROS package on {}".format(copter.copter_id))
else:
path_to_send = client_path
copter.client.send_file(file, os.path.join(path_to_send, filename))
if callback is not None:
callback(copter)
def send_files(self, prompt, ext_filter, copters=None, client_path="", client_filename="", match_id=False,
onefile=False, callback=None):
onefile=False, callback=None, clover_dir=False):
if onefile:
file = QFileDialog.getOpenFileName(self, prompt, filter=ext_filter)[0]
files = [file] if file else []
@@ -416,10 +426,10 @@ class MainWindow(QtWidgets.QMainWindow):
if not files:
return
self._send_files(files, copters, client_path, client_filename, match_id, callback)
self._send_files(files, copters, client_path, client_filename, match_id, callback, clover_dir)
def send_directory_files(self, prompt, extensions=(), copters=None, client_path="", client_filename="",
match_id=False, callback=None):
match_id=False, callback=None, clover_dir=False):
path = QFileDialog.getExistingDirectory(self, prompt)
if not path:
@@ -431,7 +441,7 @@ class MainWindow(QtWidgets.QMainWindow):
patterns = [path+'/*.*']
files = multi_glob(*patterns)
self._send_files(files, copters, client_path, client_filename, match_id, callback)
self._send_files(files, copters, client_path, client_filename, match_id, callback, clover_dir)
def request_any_file(self, client_path=None, copters=None):
if client_path is None:
@@ -485,25 +495,21 @@ class MainWindow(QtWidgets.QMainWindow):
@pyqtSlot()
def send_calibrations(self):
self.send_directory_files("Select directory with calibrations", ('.yaml', ), match_id=True,
client_path=os.path.join(self.server.config.client_clever_dir,"camera_info/"),
client_filename="calibration.yaml") # TODO callback to reload clever?
# from os.path import expanduser # TODO on client
# home = expanduser("~") -> "~catkin_ws/src/clever/clever/camera_info/"
client_path="camera_info", client_filename="calibration.yaml", clover_dir=True)
@pyqtSlot()
def send_aruco(self):
def callback(copter):
copter.client.send_message("service_restart", kwargs={"name": "clever"})
copter.client.send_message("service_restart", kwargs={"name": "clover"})
self.send_files("Select aruco map configuration file", "Aruco map files (*.txt)", onefile=True,
client_path=os.path.abspath(os.path.join(self.server.config.client_clever_dir,"../aruco_pose/map/")),
client_filename="animation_map.txt", callback=callback)
client_path="../aruco_pose/map/", client_filename="animation_map.txt",
callback=callback, clover_dir=True)
@pyqtSlot()
def send_launch(self):
self.send_directory_files("Select directory with launch files", ('.launch', '.yaml'), match_id=False,
client_path=os.path.join(self.server.config.client_clever_dir,"launch/")) # TODO clever restart callback?
client_path="launch", clover_dir=True)
@pyqtSlot()
def send_fcu_parameters(self):
@@ -546,7 +552,6 @@ class MainWindow(QtWidgets.QMainWindow):
@pyqtSlot()
def restart_clever_show(self):
for copter in self.model.user_selected():
copter.client.send_message("service_restart", kwargs={"name": "visual_pose_watchdog"})
copter.client.send_message("service_restart", kwargs={"name": "clever-show"})
@pyqtSlot()
@@ -623,7 +628,7 @@ class MainWindow(QtWidgets.QMainWindow):
def save_callback():
config.write()
ConfigDialog().call_config_dialog(config, save_callback, restart, name="Server config")
ConfigDialog().call_config_dialog(config, save_callback, restart, name="server config")
def register_callbacks(self):
@messaging.message_callback("telemetry")
@@ -683,7 +688,7 @@ if __name__ == "__main__":
# app.exec_()
with loop:
server = ServerQt()
server = ServerQt(config_path="config/server.ini")
window = MainWindow(server)
Client.on_first_connect = window.new_client_connected

View File

@@ -4,12 +4,12 @@ import shutil
from pytest import approx
import pytest
# Add parent dir to PATH to import config
import inspect
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)
sys.path.insert(0, os.path.join(parent_dir,"Drone"))
# Add parent dir to PATH to import messaging_lib and config_lib
current_dir = (os.path.dirname(os.path.realpath(__file__)))
lib_dir = os.path.realpath(os.path.join(current_dir, '../lib'))
drone_dir = os.path.realpath(os.path.join(current_dir, '../drone'))
sys.path.insert(0, lib_dir)
sys.path.insert(0, drone_dir)
from config import ConfigManager
@@ -23,29 +23,29 @@ if not os.path.exists(spec_path):
else:
print("Successfully created the directory {}".format(spec_path))
shutil.copy("../Drone/config/spec/configspec_client.ini", spec_path)
shutil.copy("../drone/config/spec/configspec_client.ini", spec_path)
config = ConfigManager()
config.load_config_and_spec(os.path.join(config_path,'client.ini'))
assert config.config_name == "client"
import animation_lib
import modules.animation as animation
a = animation_lib.Animation()
a = animation.Animation()
def test_animation_1():
a.update_frames(config, "animation_1.csv")
a.update_frames(config, "assets/animation_1.csv")
assert a.id == 'basic'
assert approx(a.original_frames[0].get_pos()) == [0,0,0]
assert a.original_frames[0].get_color() == [204,2,0]
assert a.original_frames[0].pose_is_valid()
assert animation_lib.get_numbers(a.static_begin_frames) == range(1,11)
assert animation_lib.get_numbers(a.takeoff_frames) == range(11,21)
assert animation_lib.get_numbers(a.route_frames) == range(21,31)
assert animation_lib.get_numbers(a.land_frames) == range(31, 41)
assert animation_lib.get_numbers(a.static_end_frames) == range(41, 51)
assert animation_lib.get_numbers(a.output_frames) == range(11,31)
assert animation.get_numbers(a.static_begin_frames) == range(1,11)
assert animation.get_numbers(a.takeoff_frames) == range(11,21)
assert animation.get_numbers(a.route_frames) == range(21,31)
assert animation.get_numbers(a.land_frames) == range(31, 41)
assert animation.get_numbers(a.static_end_frames) == range(41, 51)
assert animation.get_numbers(a.output_frames) == range(11,31)
assert approx(a.static_begin_time) == 1
assert approx(a.takeoff_time) == 1
assert approx(a.output_frames_min_z) == 0.1
@@ -54,17 +54,17 @@ def test_animation_1():
assert approx(a.get_start_point(ratio=[1,2,3], offset=[4,5,6])) == [4.,5.,6.3]
def test_animation_2():
a.update_frames(config, "animation_2.csv")
a.update_frames(config, "assets/animation_2.csv")
assert a.id == 'parad'
assert approx(a.original_frames[271].get_pos()) == [-1.00519,2.65699,0.24386]
assert a.original_frames[271].get_color() == [7,255,0]
assert a.original_frames[271].pose_is_valid()
assert animation_lib.get_numbers(a.static_begin_frames) == range(271)
assert animation_lib.get_numbers(a.takeoff_frames) == range(271,285)
assert animation_lib.get_numbers(a.route_frames) == range(285,1065)
assert animation_lib.get_numbers(a.land_frames) == []
assert animation_lib.get_numbers(a.static_end_frames) == []
assert animation_lib.get_numbers(a.output_frames) == range(271, 1065)
assert animation.get_numbers(a.static_begin_frames) == range(271)
assert animation.get_numbers(a.takeoff_frames) == range(271,285)
assert animation.get_numbers(a.route_frames) == range(285,1065)
assert animation.get_numbers(a.land_frames) == []
assert animation.get_numbers(a.static_end_frames) == []
assert animation.get_numbers(a.output_frames) == range(271, 1065)
assert approx(a.static_begin_time) == 27.1
assert approx(a.takeoff_time) == 1.4
assert approx(a.output_frames_min_z) == 0.24386
@@ -73,16 +73,16 @@ def test_animation_2():
assert approx(a.get_start_point(ratio=[1,2,3], offset=[4,5,6])) == [2.99481, 10.31398, 6.73158]
def test_animation_3():
a.update_frames(config, "animation_3.csv")
a.update_frames(config, "assets/animation_3.csv")
assert a.id == 'route'
assert approx(a.original_frames[9].get_pos()) == [0.97783,0.0,1.0]
assert a.original_frames[9].get_color() == [0,204,2]
assert a.original_frames[9].pose_is_valid()
assert animation_lib.get_numbers(a.static_begin_frames) == []
assert animation_lib.get_numbers(a.takeoff_frames) == []
assert animation_lib.get_numbers(a.route_frames) == range(20,31)
assert animation_lib.get_numbers(a.land_frames) == []
assert animation_lib.get_numbers(a.static_end_frames) == []
assert animation.get_numbers(a.static_begin_frames) == []
assert animation.get_numbers(a.takeoff_frames) == []
assert animation.get_numbers(a.route_frames) == range(20,31)
assert animation.get_numbers(a.land_frames) == []
assert animation.get_numbers(a.static_end_frames) == []
assert approx(a.static_begin_time) == 0
assert approx(a.takeoff_time) == 0
assert approx(a.output_frames_min_z) == 1
@@ -91,17 +91,17 @@ def test_animation_3():
assert approx(a.get_start_point(ratio=[1,2,3], offset=[4,5,6])) == [4,5,9]
def test_animation_4():
a.update_frames(config, "animation_4.csv")
a.update_frames(config, "assets/animation_4.csv")
assert a.id == 'two_drones_test'
assert approx(a.original_frames[11].get_pos()) == [0.21774,1.4,1.0]
assert a.original_frames[11].get_color() == [0,0,0]
assert a.original_frames[11].pose_is_valid()
assert animation_lib.get_numbers(a.static_begin_frames) == range(1,12)
assert animation_lib.get_numbers(a.takeoff_frames) == []
assert animation_lib.get_numbers(a.route_frames) == range(12,141)
assert animation_lib.get_numbers(a.land_frames) == []
assert animation_lib.get_numbers(a.static_end_frames) == range(141,161)
assert animation_lib.get_numbers(a.output_frames) == range(12,141)
assert animation.get_numbers(a.static_begin_frames) == range(1,12)
assert animation.get_numbers(a.takeoff_frames) == []
assert animation.get_numbers(a.route_frames) == range(12,141)
assert animation.get_numbers(a.land_frames) == []
assert animation.get_numbers(a.static_end_frames) == range(141,161)
assert animation.get_numbers(a.output_frames) == range(12,141)
assert approx(a.static_begin_time) == 1.1
assert approx(a.takeoff_time) == 0
assert approx(a.output_frames_min_z) == 1
@@ -114,11 +114,11 @@ def test_animation_no_file():
assert a.id == None
assert a.original_frames == []
assert a.output_frames == []
assert animation_lib.get_numbers(a.static_begin_frames) == []
assert animation_lib.get_numbers(a.takeoff_frames) == []
assert animation_lib.get_numbers(a.route_frames) == []
assert animation_lib.get_numbers(a.land_frames) == []
assert animation_lib.get_numbers(a.static_end_frames) == []
assert animation.get_numbers(a.static_begin_frames) == []
assert animation.get_numbers(a.takeoff_frames) == []
assert animation.get_numbers(a.route_frames) == []
assert animation.get_numbers(a.land_frames) == []
assert animation.get_numbers(a.static_end_frames) == []
assert a.static_begin_time == 0
assert a.takeoff_time == 0
assert a.output_frames_min_z is None
@@ -127,12 +127,12 @@ def test_animation_no_file():
assert a.get_start_point(ratio=[1,2,3], offset=[4,5,6]) == []
# print animation_lib.get_numbers(a.static_begin_frames)
# print animation_lib.get_numbers(a.takeoff_frames)
# print animation_lib.get_numbers(a.route_frames)
# print animation_lib.get_numbers(a.land_frames)
# print animation_lib.get_numbers(a.static_end_frames)
# print animation_lib.get_numbers(a.output_frames)
# print animation.get_numbers(a.static_begin_frames)
# print animation.get_numbers(a.takeoff_frames)
# print animation.get_numbers(a.route_frames)
# print animation.get_numbers(a.land_frames)
# print animation.get_numbers(a.static_end_frames)
# print animation.get_numbers(a.output_frames)
# print a.static_begin_time
# print a.takeoff_time
# print a.output_frames_min_z

View File

@@ -1,7 +1,7 @@
import os
import shutil
import config
from Server.copter_table_models import CopterDataModel
from server.copter_table_models import CopterDataModel
from config import ConfigManager, ConfigObj