Merge pull request #39 from artem30801/master

Merge with master to maintain project structure
This commit is contained in:
artem30801
2019-07-28 12:20:20 +03:00
committed by GitHub
26 changed files with 678 additions and 74 deletions

View File

@@ -7,11 +7,10 @@ assignees: ''
---
**Describe the bug**
**The bug description**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
**Steps to reproduce the behavior**
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@@ -23,16 +22,8 @@ A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Version**
v0.3-alpha.2 (for example)
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
**Additional information**
Add any other information about the problem here.

View File

@@ -7,7 +7,7 @@ assignees: ''
---
**Is your feature request related to a problem? Please describe.**
**Request description**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**

View File

@@ -9,7 +9,7 @@ env:
- if [[ -z ${TRAVIS_TAG} ]]; then IMAGE_VERSION="${TRAVIS_COMMIT}}"; else IMAGE_VERSION="${TRAVIS_TAG}"; fi
- IMAGE_NAME="$(basename -s '.git' ${TARGET_REPO})_${IMAGE_VERSION}.img"
git:
depth: 50
depth: false
jobs:
fast_finish: true
include:

View File

@@ -150,7 +150,6 @@ def _command_led_fill(**kwargs):
def _copter_flip():
FlightLib.flip(frame_id=client.active_client.FRAME_ID)
@messaging.message_callback("takeoff")
def _command_takeoff(**kwargs):
task_manager.add_task(time.time(), 0, animation.takeoff,

View File

@@ -1,15 +1,15 @@
# CleverSwarm
Програмное обеспечение для запуска шоу дронов под управлением Raspberry Pi с пакетом COEX Clever.
# clever-show
[![Build Status](https://travis-ci.org/artem30801/CleverSwarm.svg?branch=master)](https://travis-ci.org/artem30801/CleverSwarm)
### Пакет включает в себя:
* Набор ПО для дрона, включащее в себя библиотеку для автономного полёта, модуль для воспроизведения анимаций и клиентское приложение для удаленного синхронизированного управления
* Серверное приложение для удаленного синхронизированного управления дронами и удобной передачи анимации
Програмное обеспечение для запуска шоу дронов под управлением Raspberry Pi с пакетом COEX [Clever](https://github.com/copterexpress/clever).
## Установка
Скачайте или склонируйте этот репозиторий на компьютер и дроны:
```bash
git clone https://github.com/artem30801/CleverSwarm.git
```
Для дальнейших инструкций перейдите на Wiki
### Пакет включает в себя:
* [Набор ПО для дрона](https://github.com/artem30801/CleverSwarm/tree/master/Drone), включащее в себя библиотеку для автономного полёта, модуль для воспроизведения анимаций и клиентское приложение для удаленного синхронизированного управления
* [Серверное приложение](https://github.com/artem30801/CleverSwarm/tree/master/Server) для удаленного синхронизированного управления дронами и удобной настройки системы для воспроизведения анимации
* [Аддон для Blender 2.8](https://github.com/artem30801/CleverSwarm/tree/master/blender-addon) для преобразования анимации полёта коптеров, созданной в Blender, в файлы полётов для каждого коптера
* [Образ для Raspberry Pi](https://github.com/artem30801/CleverSwarm/releases/latest) для быстрого запуска ПО на коптере
## Документация
Инструкция по запуску ПО находится [здесь](docs/start-tutorial.md).
Подробная документация расположена в папке [docs](https://github.com/artem30801/CleverSwarm/tree/master/docs).

5
Server/chrony.conf Normal file
View File

@@ -0,0 +1,5 @@
server master iburst
driftfile /var/lib/chrony/drift
allow 192.168.0.0/16
makestep 1.0 3
rtcsync

View File

@@ -65,17 +65,22 @@ class Server:
def load_config(self):
self.config.read(self.config_path)
self.port = int(self.config['SERVER']['port']) # TODO try, init def
self.broadcast_port = int(self.config['SERVER']['broadcast_port'])
self.BROADCAST_DELAY = int(self.config['SERVER']['broadcast_delay'])
Server.BUFFER_SIZE = int(self.config['SERVER']['buffer_size'])
self.use_broadcast = self.config.getboolean('BROADCAST', 'use_broadcast')
self.broadcast_port = int(self.config['BROADCAST']['broadcast_port'])
self.BROADCAST_DELAY = int(self.config['BROADCAST']['broadcast_delay'])
self.USE_NTP = self.config.getboolean('NTP', 'use_ntp')
self.NTP_HOST = self.config['NTP']['host']
self.NTP_PORT = int(self.config['NTP']['port'])
def start(self): # do_auto_connect=True, do_ip_broadcast=True, do_listen_broadcast=False
def start(self, do_ip_broadcast=None): # do_auto_connect=True, , do_listen_broadcast=False
self.time_started = time.time()
if do_ip_broadcast is None:
do_ip_broadcast = self.use_broadcast
logging.info("Starting server with id: {} on {}:{} !".format(self.id, self.ip, self.port))
logging.info("Starting server socket!")
self.server_socket.bind((self.ip, self.port))
@@ -84,9 +89,10 @@ class Server:
self.client_processor_thread_running.set()
self.autoconnect_thread.start()
logging.info("Starting broadcast sender thread!")
self.broadcast_thread_running.set()
self.broadcast_thread.start()
if do_ip_broadcast:
logging.info("Starting broadcast sender thread!")
self.broadcast_thread_running.set()
self.broadcast_thread.start()
logging.info("Starting broadcast listener thread!")
self.listener_thread_running.set()

View File

@@ -1,8 +1,11 @@
[SERVER]
port = 25000
buffer_size = 1024
[BROADCAST]
use_broadcast = True
broadcast_port = 8181
broadcast_delay = 5
buffer_size = 1024
[NTP]
use_ntp = False

View File

@@ -143,14 +143,14 @@ class MainWindow(QtWidgets.QMainWindow):
@pyqtSlot()
def takeoff_selected(self, **kwargs):
for copter in self.model.user_selected():
if all_checks(copter):
if takeoff_checks(copter):
copter.client.send_message("takeoff")
@confirmation_required("This operation will flip(!!!) copters immediately. Proceed?")
@pyqtSlot()
def flip(self, **kwargs):
for copter in self.model.user_selected():
if all_checks(copter):
if takeoff_checks(copter):
copter.client.send_message("flip")
@pyqtSlot()

25
blender-addon/README.md Normal file
View File

@@ -0,0 +1,25 @@
# blender-csv-animation
A Blender extension that export paths of objects in blender animation to a csv files
## CSV file format
First row is the animation filename.
Every next row of the file contains following information about an object:
- frame number,
- x coordinate,
- y coordinate,
- z coordinate,
- rotaion around z-axis angle (yaw for copter),
- rgb.
## How to use it
Clone or download this repository
```bash
git clone https://github.com/artem30801/CleverSwarm.git
```
Open Blender and install the addon:
1) Open User Prerences windows using main menu or shortcut (Ctrl + Alt + U): Files - User Preferences
2) Under Add-ons tab click Install Add-on from File...
3) Choose addon.py file from the directory of this repository
4) Enable the Add-on
Use [official docs](https://docs.blender.org/manual/en/latest/preferences/addons.html) for getting additional information

213
blender-addon/addon.py Normal file
View File

@@ -0,0 +1,213 @@
import os
import csv
import math
import bpy
from bpy_extras.io_utils import ExportHelper
from bpy.types import Operator
from bpy.props import StringProperty, BoolProperty, FloatProperty, IntProperty
bl_info = {
"name": "Export > CSV Drone Swarm Animation Exporter (.csv)",
"author": "Artem Vasiunik",
"version": (0, 4, 0),
"blender": (2, 80, 0),
#"api": 36079,
"location": "File > Export > CSV Drone Swarm Animation Exporter (.csv)",
"description": "Export > CSV Drone Swarm Animation Exporter (.csv)",
"warning": "",
"wiki_url": "https://github.com/artem30801/blender-csv-animation/blob/master/README.md",
"tracker_url": "https://github.com/artem30801/blender-csv-animation/issues",
"category": "Import-Export"
}
class ExportCsv(Operator, ExportHelper):
bl_idname = "export_swarm_anim.folder"
bl_label = "Export Drone Swarm animation"
filename_ext = ''
use_filter_folder = True
use_namefilter: bpy.props.BoolProperty(
name="Use name filter for objects",
default=True,
)
drones_name: bpy.props.StringProperty(
name="Name identifier",
description="Name identifier for all drone objects",
default="copter"
)
show_warnings: bpy.props.BoolProperty(
name="Show detailed animation warnings",
default=False,
)
speed_warning_limit: bpy.props.FloatProperty(
name="Speed limit",
description="Limit of drone movement speed (m/s)",
unit='VELOCITY',
default=3,
min=0,
)
drone_distance_limit: bpy.props.FloatProperty(
name="Distance limit",
description="Closest possible distance between drones (m)",
unit='LENGTH',
default=1.5,
min=0,
)
filepath: StringProperty(
name="File Path",
description="File path used for exporting CSV files",
maxlen=1024,
subtype='DIR_PATH',
default=""
)
def execute(self, context):
create_folder_if_does_not_exist(self.filepath)
scene = context.scene
objects = context.visible_objects
drone_objects = []
if self.use_namefilter:
for drone_obj in objects:
if self.drones_name.lower() in drone_obj.name.lower():
drone_objects.append(drone_obj)
else:
drone_objects = objects
frame_start = scene.frame_start
frame_end = scene.frame_end
for drone_obj in drone_objects:
with open(os.path.join(self.filepath, '{}.csv'.format(drone_obj.name.lower())), 'w') as csv_file:
animation_file_writer = csv.writer(
csv_file,
delimiter=',',
quotechar='|',
quoting=csv.QUOTE_MINIMAL
)
speed_exeeded = False
distance_exeeded = False
prev_x, prev_y, prev_z = 0, 0, 0
animation_file_writer.writerow([
os.path.splitext(bpy.path.basename(bpy.data.filepath))[0]
])
for frame_number in range(frame_start, frame_end + 1):
scene.frame_set(frame_number)
rgb = get_rgb_from_object(drone_obj)
x, y, z = drone_obj.matrix_world.to_translation()
rot_z = drone_obj.matrix_world.to_euler('XYZ')[2]
speed = calc_speed((x, y, z), (prev_x, prev_y, prev_z)) if frame_number != frame_start else 1
prev_x, prev_y, prev_z = x, y, z
if speed > self.speed_warning_limit:
speed_exeeded = True
if self.show_warnings:
self.report({'WARNING'},
"Speed of drone '%s' is greater than %s m/s (%s m/s) on frame %s" %
(drone_obj.name, round(self.speed_warning_limit, 5), round(speed, 5), frame_number))
for second_drone_obj in drone_objects:
if second_drone_obj is not drone_obj:
x2, y2, z2 = second_drone_obj.matrix_world.to_translation()
distance = calc_distance((x, y, z), (x2, y2, z2))
if distance < self.drone_distance_limit:
distance_exeeded = True
if self.show_warnings:
self.report({'WARNING'},
"Distance beteween drones '%s' and '%s' is less than %s m (%s m) on frame %s" %
(drone_obj.name, second_drone_obj.name,
round(self.drone_distance_limit, 5), round(distance, 5), frame_number))
animation_file_writer.writerow([
str(frame_number),
round(x, 5), round(y, 5), round(z, 5),
round(rot_z, 5),
*rgb,
])
if speed_exeeded:
self.report({'WARNING'}, "Drone '%s' speed limits exeeded" % drone_obj.name)
if distance_exeeded:
self.report({'WARNING'}, "Drone '%s' distance limits exeeded" % drone_obj.name)
self.report({'WARNING'}, "Animation file exported for drone '%s'" % drone_obj.name)
return {'FINISHED'}
def create_folder_if_does_not_exist(folder_path):
if os.path.isdir(folder_path):
return
os.mkdir(folder_path)
def get_rgb_from_object(obj):
rgb = [0, 0, 0]
try:
if len(obj.material_slots) > 0:
print('material slots true')
for slot in obj.material_slots:
if "led_color" in slot.name.lower():
print('led color')
if slot.material.use_nodes:
for node in slot.material.node_tree.nodes:
if node.type in ('EMISSION', 'BSDF_DIFFUSE'):
alpha = node.inputs[0].default_value[3]
for component in range(3):
rgb[component] = int(node.inputs[0].default_value[component] * alpha * 255)
else:
print('no led color')
for component in range(3):
rgb[component] = int(slot.material.diffuse_color[component] * 255)
except AttributeError:
pass
finally:
return rgb
def calc_speed(start_point, end_point):
time_delta = 0.1
distance = calc_distance(start_point, end_point)
return distance / time_delta
def calc_distance(start_point, end_point):
distance = math.sqrt(
(start_point[0] - end_point[0]) ** 2 +
(start_point[1] - end_point[1]) ** 2 +
(start_point[2] - end_point[2]) ** 2
)
return distance
def menu_func(self, context):
self.layout.operator(
ExportCsv.bl_idname,
text="CSV Drone Swarm Animation Exporter (.csv)"
)
def register():
bpy.utils.register_class(ExportCsv)
bpy.types.TOPBAR_MT_file_export.append(menu_func)
def unregister():
bpy.utils.unregister_class(ExportCsv)
bpy.types.TOPBAR_MT_file_export.remove(menu_func)
if __name__ == "__main__":
register()

View File

@@ -1,25 +0,0 @@
0 0.3375 0.0 4.6 0 0 0 0
1 0.3375 1.15 4.6 0 0 0 0
2 0.3375 2.3 4.6 0 0 0 0
3 0.3375 3.45 4.6 0 0 0 0
4 0.3375 4.6 4.6 0 0 0 0
5 0.3375 0.0 3.45 0 0 0 0
6 0.3375 1.15 3.45 0 0 0 0
7 0.3375 2.3 3.45 0 0 0 0
8 0.3375 3.45 3.45 0 0 0 0
9 0.3375 4.6 3.45 0 0 0 0
10 0.3375 0.0 2.3 0 0 0 0
11 0.3375 1.15 2.3 0 0 0 0
12 0.3375 2.3 2.3 0 0 0 0
13 0.3375 3.45 2.3 0 0 0 0
14 0.3375 4.6 2.3 0 0 0 0
15 0.3375 0.0 1.15 0 0 0 0
16 0.3375 1.15 1.15 0 0 0 0
17 0.3375 2.3 1.15 0 0 0 0
18 0.3375 3.45 1.15 0 0 0 0
19 0.3375 4.6 1.15 0 0 0 0
20 0.3375 0.0 0.0 0 0 0 0
21 0.3375 1.15 0.0 0 0 0 0
22 0.3375 2.3 0.0 0 0 0 0
23 0.3375 3.45 0.0 0 0 0 0
24 0.3375 4.6 0.0 0 0 0 0

View File

@@ -0,0 +1,37 @@
<launch>
<arg name="aruco_detect" default="true"/>
<arg name="aruco_map" default="true"/>
<arg name="aruco_vpe" default="true"/>
<!-- For additional help go to https://clever.copterexpress.com/aruco.html -->
<!-- aruco_detect: detect aruco markers, estimate poses -->
<node name="aruco_detect" pkg="nodelet" if="$(arg aruco_detect)" type="nodelet" args="load aruco_pose/aruco_detect nodelet_manager" output="screen" clear_params="true">
<remap from="image_raw" to="main_camera/image_raw"/>
<remap from="camera_info" to="main_camera/camera_info"/>
<param name="estimate_poses" value="true"/>
<param name="send_tf" value="true"/>
<param name="known_tilt" value="map"/>
<param name="length" value="0.33"/>
</node>
<!-- aruco_map: estimate aruco map pose -->
<node name="aruco_map" pkg="nodelet" type="nodelet" if="$(arg aruco_map)" args="load aruco_pose/aruco_map nodelet_manager" output="screen" clear_params="true">
<remap from="image_raw" to="main_camera/image_raw"/>
<remap from="camera_info" to="main_camera/camera_info"/>
<remap from="markers" to="aruco_detect/markers"/>
<param name="map" value="$(find aruco_pose)/map/animation_map.txt"/>
<param name="known_tilt" value="map"/>
<param name="frame_id" value="aruco_map_detected" if="$(arg aruco_vpe)"/>
<param name="frame_id" value="aruco_map" unless="$(arg aruco_vpe)"/>
</node>
<!-- vpe publisher from aruco markers -->
<node name="vpe_publisher" pkg="clever" type="vpe_publisher" if="$(arg aruco_vpe)" output="screen" clear_params="true">
<remap from="~pose_cov" to="aruco_map/pose"/>
<remap from="~vpe" to="mavros/vision_pose/pose"/>
<param name="frame_id" value="aruco_map_detected"/>
<param name="publish_zero" value="true"/>
<param name="offset_frame_id" value="aruco_map"/>
</node>
</launch>

View File

@@ -0,0 +1,71 @@
<launch>
<arg name="fcu_conn" default="usb"/>
<arg name="fcu_ip" default="127.0.0.1"/>
<arg name="gcs_bridge" default="tcp"/>
<arg name="web_video_server" default="true"/>
<arg name="rosbridge" default="true"/>
<arg name="main_camera" default="true"/>
<arg name="optical_flow" default="true"/>
<arg name="aruco" default="true"/>
<arg name="rc" default="true"/>
<arg name="rangefinder_vl53l1x" default="true"/>
<!-- mavros -->
<include file="$(find clever)/launch/mavros.launch">
<arg name="fcu_conn" value="$(arg fcu_conn)"/>
<arg name="fcu_ip" value="$(arg fcu_ip)"/>
<arg name="gcs_bridge" value="$(arg gcs_bridge)"/>
</include>
<!-- web video server -->
<node name="web_video_server" pkg="web_video_server" type="web_video_server" if="$(arg web_video_server)" required="false" respawn="true" respawn_delay="5">
<param name="default_stream_type" value="ros_compressed"/>
<param name="publish_rate" value="1.0"/>
</node>
<!-- aruco markers -->
<include file="$(find clever)/launch/aruco.launch" if="$(arg aruco)"/>
<!-- optical flow -->
<node pkg="nodelet" type="nodelet" name="optical_flow" args="load clever/optical_flow nodelet_manager" if="$(arg optical_flow)" clear_params="true" output="screen">
<remap from="image_raw" to="main_camera/image_raw"/>
<remap from="camera_info" to="main_camera/camera_info"/>
<param name="calc_flow_gyro" value="true"/>
</node>
<!-- main nodelet manager -->
<node pkg="nodelet" type="nodelet" name="nodelet_manager" args="manager" output="screen" clear_params="true">
<param name="num_worker_threads" value="2"/>
</node>
<node pkg="tf2_ros" type="static_transform_publisher" name="map_flipped_frame" args="0 0 0 3.1415926 3.1415926 0 map map_flipped"/>
<!-- simplified offboard control -->
<node name="simple_offboard" pkg="clever" type="simple_offboard" output="screen" clear_params="true">
<param name="reference_frames/body" value="map"/>
<param name="reference_frames/base_link" value="map"/>
</node>
<!-- Auxiliary frames -->
<node name="frames" pkg="clever" type="frames" output="screen">
<param name="body/frame_id" value="body"/>
</node>
<!-- main camera -->
<include file="$(find clever)/launch/main_camera.launch" if="$(arg main_camera)"/>
<!-- rosbridge -->
<include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch" if="$(eval rosbridge or rc)"/>
<!-- tf2 republisher for web visualization -->
<node name="tf2_web_republisher" pkg="tf2_web_republisher" type="tf2_web_republisher" output="screen" if="$(arg rosbridge)"/>
<!-- vl53l1x ToF rangefinder -->
<node name="vl53l1x" pkg="vl53l1x" type="vl53l1x_node" output="screen" if="$(arg rangefinder_vl53l1x)">
<param name="frame_id" value="rangefinder"/>
<remap from="~range" to="mavros/distance_sensor/rangefinder_sub"/> <!-- redirect data to FCU -->
</node>
<!-- rc backend -->
<node name="rc" pkg="clever" type="rc" output="screen" if="$(arg rc)"/>
</launch>

View File

@@ -0,0 +1,37 @@
<launch>
<!-- Camera position and orientation are represented by base_link -> main_camera_optical transform -->
<!-- static_transform_publisher arguments: x y z yaw pitch roll frame_id child_frame_id -->
<!-- article about camera setup: https://clever.copterexpress.com/camera_frame.html -->
<!-- camera is oriented downward, camera cable goes backward [option 1] -->
<node pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 -0.07 -1.5707963 0 3.1415926 base_link main_camera_optical"/>
<!-- camera is oriented downward, camera cable goes forward [option 2] -->
<!--<node pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 -0.07 1.5707963 0 3.1415926 base_link main_camera_optical"/>-->
<!-- camera is oriented upward, camera cable goes backward [option 3] -->
<!--<node pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 0.07 1.5707963 0 0 base_link main_camera_optical"/>-->
<!-- camera is oriented upward, camera cable goes forward [option 4] -->
<!--<node pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 0.07 -1.5707963 0 0 base_link main_camera_optical"/>-->
<!-- camera node -->
<node pkg="nodelet" type="nodelet" name="main_camera" args="load cv_camera/CvCameraNodelet nodelet_manager" clear_params="true">
<param name="frame_id" value="main_camera_optical"/>
<param name="camera_info_url" value="file://$(find clever)/camera_info/fisheye_cam_320.yaml"/>
<param name="rate" value="100"/> <!-- poll rate -->
<param name="cv_cap_prop_fps" value="40"/> <!-- camera FPS -->
<param name="capture_delay" value="0.02"/> <!-- approximate delay on frame retrieving -->
<!-- camera resolution, NOTE: camera_info file should match it -->
<param name="image_width" value="320"/>
<param name="image_height" value="240"/>
</node>
<!-- camera visualization markers -->
<node pkg="clever" type="camera_markers" ns="main_camera" name="main_camera_markers">
<param name="scale" value="3.0"/>
</node>
</launch>

View File

@@ -0,0 +1,8 @@
107 0.33 0 0 0 0 0 0
106 0.33 0.77 0 0 0 0 0
105 0.33 0 0.77 0 0 0 0
104 0.33 0.77 0.77 0 0 0 0
103 0.33 0 1.54 0 0 0 0
102 0.33 0.77 1.54 0 0 0 0
101 0.33 0 2.31 0 0 0 0
100 0.33 0.77 2.31 0 0 0 0

View File

@@ -30,10 +30,12 @@ echo_stamp() {
REPO_DIR="/mnt"
SCRIPTS_DIR="${REPO_DIR}/builder"
CONFIG_DIR="${SCRIPTS_DIR}/clever-config"
IMAGES_DIR="${REPO_DIR}/images"
[[ ! -d ${SCRIPTS_DIR} ]] && (echo_stamp "Directory ${SCRIPTS_DIR} doesn't exist" "ERROR"; exit 1)
[[ ! -d ${IMAGES_DIR} ]] && mkdir ${IMAGES_DIR} && echo_stamp "Directory ${IMAGES_DIR} was created successful" "SUCCESS"
[[ ! -d ${CONFIG_DIR} ]] && mkdir ${CONFIG_DIR} && echo_stamp "Directory ${CONFIG_DIR} was created successful" "SUCCESS"
if [[ -z ${TRAVIS_TAG} ]]; then IMAGE_VERSION="$(cd ${REPO_DIR}; git log --format=%h -1)"; else IMAGE_VERSION="${TRAVIS_TAG}"; fi
# IMAGE_VERSION="${TRAVIS_TAG:=$(cd ${REPO_DIR}; git log --format=%h -1)}"
@@ -105,7 +107,10 @@ git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
# Copy service file for clever show client
img-chroot ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/clever-show.service' '/lib/systemd/system/'
img-chroot ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/animation_map.txt' '/home/pi/catkin_ws/src/clever/aruco_pose/map/'
# Copy config files for clever
if [[ -d "${CONFIG_DIR}/launch" ]]; then img-chroot ${IMAGE_PATH} copy ${CONFIG_DIR}'/launch' '/home/pi/catkin_ws/src/clever/clever'; fi
if [[ -d "${CONFIG_DIR}/map" ]]; then img-chroot ${IMAGE_PATH} copy ${CONFIG_DIR}'/map' '/home/pi/catkin_ws/src/clever/aruco_pose'; fi
# Shrink image
img-resize ${IMAGE_PATH}

View File

@@ -23,7 +23,7 @@ echo_stamp() {
}
# rename wifi ssid
sed -i "s/NEW_SSID='CLEVER/NEW_SSID='CleverShow/" /root/init_rpi.sh
sed -i "s/NEW_SSID='CLEVER/NEW_SSID='CLEVERSHOW/" /root/init_rpi.sh
# add sudoers variables to make sudo works with ros (for led strip)
grep -qxF 'Defaults env_keep += "ROS_LOG_DIR"' /etc/sudoers || cat << EOT >> /etc/sudoers
@@ -38,13 +38,5 @@ Defaults env_keep += "ROS_HOME"
Defaults env_keep += "ROS_LOG_DIR"
EOT
# configure aruco.launch and clever.launch (for positioning with aruco map)
sed -i '/<arg name="aruco_map"/c \ <arg name="aruco_map" default="true"/>' /home/pi/catkin_ws/src/clever/clever/launch/aruco.launch
sed -i '/<arg name="aruco_vpe"/c \ <arg name="aruco_vpe" default="true"/>' /home/pi/catkin_ws/src/clever/clever/launch/aruco.launch
sed -i '/<param name="map"/c \ <param name="map" value="\$\(find aruco_pose\)/map/animation_map.txt"/>' /home/pi/catkin_ws/src/clever/clever/launch/aruco.launch
sed -i '/<arg name="aruco"/c \ <arg name="aruco" default="true"/>' /home/pi/catkin_ws/src/clever/clever/launch/clever.launch
sed -i '/<arg name="rangefinder_vl53l1x"/c \ <arg name="rangefinder_vl53l1x" default="true"/>' /home/pi/catkin_ws/src/clever/clever/launch/clever.launch
sed -i '/<arg name="optical_flow"/c \ <arg name="optical_flow" default="true"/>' /home/pi/catkin_ws/src/clever/clever/launch/clever.launch
echo_stamp "Image was configured!" "SUCCESS"

23
docs/blender-addon.md Normal file
View File

@@ -0,0 +1,23 @@
# Установка и настройка аддона
## Установка
1. Скачайте [аддон](https://github.com/artem30801/blender-csv-animation) для экспорта анимации из Blender в полётные пути для коптеров.
2. Скачайте и установите согласно инструкциям последнюю версию Blender 2.8 (beta) с [оффициального сайта](https://builder.blender.org/download/) или при использовании OS Linux через команду терминала:
```bash
snap install blender --channel=beta --classic
```
3. Откройте Blender, в верхнем меню выберите `Edit > Preferences`. В открывшемся окне настроек в боковой панели выберите пункт `Add-ons`. Нажмите на кнопку `Install...` в верхнем правом углу окна. В диалоговом окне откройте путь к папке со склонированным репозиторием проекта и выберите файл `addon.py` по пути [`blender-csv-animation/addon.py`](https://github.com/artem30801/blender-csv-animation/blob/master/addon.py). Нажмите `Install Add-on from file...`. Аддон установлен.
## Активация
В выпадающем списке `All` выберите пункт `User`. Поставьте "галочку" напротив аддона `Import-Export: Export > CSV Drone Swarm Animation Exporter` для активации аддона. Аддон активирован и готов к работе. Выполнение этих операций не понадобится при дальнейших запусках Blender.
## Дополнительно
Для деактивации аддона уберите "галочку" напротив имени аддона, как описано в предыдущем пункте. Для получения дополнительных сведений (версия, путь к файлу...) нажмите знак стрелочки слева от поля активации. В развернувшемся блоке так же есть кнопки: `Documentation` - ведет на страницу документации аддона (вы тут); `Report a bug` - ведет на страницу багтрекера на репозитории аддона; `Remove` - удалят (деинсталлирует) аддон (перед установокой новой версии рекомендуется удалить старую).
# Подготовка и создание анимации дронов
...
[Пример](https://github.com/artem30801/blender-csv-animation/blob/master/Examples/copter_base_animation.blend) можно использовать в качестве шаблона.
# Экпорт при помощи аддона
Для вызова диалогового окна экспорта нажмите в верхнем меню `File > Export > CSV Drone Swarm Animation Exporter`. В открывшемся окне экспорта необходимо выбрать целевой путь экспорта и название папки, которую создаст аддон в процессе экспорта. В боковом меню доступна панель параметров экспорта:
* `Use name filter for objects` - при отключении этого параметра будут экспортированы _все видимые объекты_
* `Name identifier`
* `Show detailed animation warnings` -
* `Speed limit` - при нарушении указанного ограничения по скорости передвижения дронов будут выведены предупреждения
* `Distance limit` - при нарушении указанной минимальной дистанции между дронами будут выведены предупреждения
После настройки (при необходимости) нужных параметров нажмите кнопку `Export Drone Swarm animation`

0
docs/client.md Normal file
View File

45
docs/image-building.md Normal file
View File

@@ -0,0 +1,45 @@
# Сборка модифицированного образа
Иногда возникает необходимость собрать образ с настройками коптера, отличными от релизной версии образа. Есть несколько способов это сделать.
## Подготовка к сборке
Установите [docker](https://www.docker.com):
```bash
sudo apt install docker.io
```
## Локальная сборка с изменением настроек Клевера
* Замените файлы настроек Клевера (launch файлы и карту) в [папке](../builder/clever-config) `builder/clever-config` в директории с исходным кодом CleverSwarm.
* Соберите свой образ с помощью docker:
```bash
cd source-dir
sudo docker run --privileged -it --rm -v /dev:/dev -v $(pwd):/mnt goldarte/img-tool:v0.5
```
## Ручная настройка образа
* Разархивируйте файл со скачанным образом, перейдите в директорию с этим образом, и войдите в консоль сборщика образа с помощью команды:
```bash
cd image-dir
sudo docker run --privileged -it --rm -v /dev:/dev -v $(pwd):/mnt goldarte/img-tool:v0.5 img-chroot /mnt/<IMAGE>
```
где `<IMAGE>` - имя файла образа. В открывшемся терминале с помощью стандартных программ (nano, git, cp, apt-get) вы можете донастроить образ.
* Внешние файлы вы можете перенести в образ с помощью команды:
```bash
sudo docker run --privileged -it --rm -v /dev:/dev -v $(pwd):/mnt goldarte/img-tool:v0.5 img-chroot /mnt/<IMAGE> copy /mnt/<MOVE_FILE> <MOVE_TO>
```
где `<MOVE_FILE>` - файл, который нужно перенести в образ (расположение относительно папки с образом, например `../builder/assets/clever-show.service`), а `<MOVE_TO>` - путь в образе, куда нужно переместить файл.
* Если в образе не хватает места для всех необходимых файлов, можно расширить образ с помощью команды:
```bash
sudo docker run --privileged -it --rm -v /dev:/dev -v $(pwd):/mnt goldarte/img-tool:v0.5 img-resize /mnt/<IMAGE> max <SIZE>
```
где `<SIZE>` - размер в байтах. Например 5G будет означать 5GB, а 5M - 5MB.
* После расширения образа его можно сжать до минимального размера + 10МB командой
```bash
sudo docker run --privileged -it --rm -v /dev:/dev -v $(pwd):/mnt goldarte/img-tool:v0.5 img-resize /mnt/<IMAGE> min
```
## Изменение скриптов сборки
Статья по изменению скриптов сборки образа и создания кастомной сборки написана [здесь](https://clever.copterexpress.com/ru/image_building.html)

103
docs/server.md Normal file
View File

@@ -0,0 +1,103 @@
# Настройка сервера
## Файл конфигурации
Конфигурация сервера задаётся в файле Server/server_config.ini, имеющем вид (по умолчанию):
```ini
[SERVER]
port = 25000
buffer_size = 1024
[BROADCAST]
use_broadcast = True
broadcast_port = 8181
broadcast_delay = 5
[NTP]
use_ntp = False
host = ntp1.stratum2.ru
port = 123
```
Конфигурация по умолчанию является полностью работоспособной и не требует изменений для быстрого начала работы системы.
### Раздел 'Server'
В этом разделе задаются параметры сетевого взаимодействия сервера, доступны следующие параметры:
* `port` - TCP порт, на котором будут приниматься входящие соединения от клиентов (коптеров). При использовании broadcast данный порт будет сконфигурирован у клиента автоматически. *Рекомендуется изменить значение по умолчанию в целях безопасности* (любое пятизначное и более число, если другое ПО не использует выбранный порт).
* `buffer_size` - размер буфера при приёме и передаче данных. *Не рекомендуется изменять. Рекомендуется использовать единое значение у сервера и клиентов.*
### Раздел 'Broadcast'
Сервер использует UDP broadcast (на адрес 255.255.255.255 с выбранным портом), чтобы передавать клиентам (коптерам) актуальную информацию о конфигурации сервера и собственном адресе сервера для подключения (IP адрес и порт сервера). Таким образом, обеспечивается автоматическое подключение клиентов к серверу без необходимости дополнительной ручной конфигурации. В данном разделе задаются параметры этого механизма.
* `use_broadcast` - будут ли использованы broadcast'ы для передачи данных (при значении `False` broadcast'ы НЕ будут отправляться). Используйте `False` в случае повышенных требований безопасности, перегруженности сети или невозможности передачи по широковещательному каналу (из-за конфигурации брандмауэра или сети)
* `broadcast_port` - UDP порт, по которому будет осуществляться отправка сообщений. *Рекомендуется изменить значение по умолчанию в целях безопасности.* **Внимание!** При изменении этого параметра клиенты НЕ смогут принимать сообщения автоконфигурации до изменения (вручную) соответствующего параметра в конфигурации клиента на равное значение.
* `broadcast_delay` - Периодичность (в секундах, целочисленное значение), с которой будет происходить отправка broadcast сообщений. Увеличьте задержку для уменьшения нагрузки на сеть. *ИЛИ* Уменьшите задержку для уменьшения времени отклика и подключения при первом запуске клиентов.
### Раздел 'NTP'
Помимо синхронизации времени (с миллисекундной точностью) с помощью пакета chrony, предоставляется альтернатива - возможность использования внешних (при наличии соединения локальной сети с интернетом) или внутрисетевых NTP-серверов. **Внимание!** Для корректной работы системы, и сервер, *и* клиенты должны использовать единый способ синхронизации времени (набор параметров в этом разделе). Данный раздел полностью унифицирован и для сервера, и для клиентов.
* `use_ntp` - Определяет, будет ли использоваться синхронизация времени с помощью NTP. (при значении `False` будет использовано локальное время ОС (синхронизируется автоматически при использовании chrony). *Рекомендуется использование crhony, а не NTP*
* `host` - имя хоста или IP адрес NTP сервера (локального или удаленного)
* `port` - порт, используемый NTP сервером
# Интерфейс сервера
Сервер имеет визуальный графический интерфейс для удобства взаимодействия.
## Глоссарий
Некоторые термины, используемые для краткости записи:
* Готовый [к полёту] коптер:
Прошедший предполётную проверку (`Preflight check`) и имеющий удовлетворительные результаты по всем необходимым столбцам (зелёные ячейки в таблице). Учитываются проверки аккумулятора и сообщения selfcheck.
* Не готовый [к полёту] коптер:
НЕ прошедший предполётную проверку (`Preflight check`) (не имеющий её результатов в таблице - жёлтые ячейки) или же имеющий НЕудовлетворительные результаты (красные ячейки в таблице). Учитываются проверки аккумулятора и сообщения selfcheck.
## Меню
### Раздел 'Actions'
Данный раздел содержит несколько утилит по отправке различных данных на *выбранные* клиенты. **Внимание!** Не пытайтесь использовать данные команды во время полёта коптеров!
* `Send Animations` - отправка файлов анимации (экспортированных аддоном к Blender) на выбранные клиенты (коптеры). В диалоговом окне необходимо выбрать *папку*, содержащую файлы анимации (автоматически создается аддоном). Каждый файл анимации будет отправлен на клиент с именем (copter ID), соответствующим имени файла без расширения.
* `Send Configurations` - отправка *единого* файла конфигурации клиента на все выбранные клиенты. В диалоговом окне необходимо выбрать *один* файл конфигурации в установленном формате. Файл конфигурации может быть неполным, в таком случае будут перезаписаны лишь указанные в файле параметры. *Не рекомендуется использовать данное действие для массовой перезаписи `Copter ID`, кроме значения `/hostname`.* **Внимание!** НЕ отправляйте на клиенты файл конфигурации сервера.
* `Send Aruco map` - отправка *единого* файла карты aruco маркеров на все выбранные клиенты. В диалоговом окне необходимо выбрать *один* файл карты в установленном формате. Файл на клиенте будет перезаписан. После получения и записи файла клиент автоматически перезапустит сервис `clever`. Для работоспособности полётных функция *необходимо подождать* некоторое время до полного запуска сервиса.
## Боковая панель инструментов (команд)
### Управление
Данный раздел команд предназначен для выскоуровневого управления роем дронов.
* Кнопка `Preflight check` - Все выбранные клиенты выполняют самодиагностику и предполётную проверку. Результаты, вместе с другими параметрами клиента, будут отображены в таблице по мере поступления данных.
* Спинбокс `Start after N seconds` - Задаёт время задержки до синхронного запуска выполнения анимаций коптерами. *Не рекомендуется использовать `0` (нулевую задержку). Для загруженных\подверженных помехам\имеющих большой пинг сетей рекомендуется использовать бо́льшие значения (>5 секунд).*
* Кнопка `Start animation` - По истечению заданного в `Start after` времени, все выбранные коптеры совершат **взлёт**ные процедуры, перелетают на стартовые точки своих анимаций и *синхронно* выполнят полётное задание (анимацию). По окончанию анимации все коптеры выполнят посадку *на месте окончания своей анимации*. Кнопка деактивирована по умолчанию и если среди выбранных коптеров есть *не готовые коптеры* (все выбранные коптеры должны быть готовыми). *При нажатии запрашивается дополнительное предупреждение!*
* Кнопка `Pause` - Ставит на 'паузу' все выбранные коптеры (их очередь заданий): приостанавливается выполнение любого полётного задания. *Используйте в чрезвычайных случаях.* **Внимание!** Данная команда НЕ прерывает полёт коптера в уже указанную точку (например: элементы взлёта, посадки; следование до начальной точки анимации и т.д.)
* Кнопка `Resume` - Все выбранные коптеры *синхронизированно* продолжат выполнение своих очередей заданий (например: исполнение анимации)
* Кнопка `Stop` - Прерывает выполнение полётных заданий *ВСЕХ* подключенных коптеров. Сбрасывает очередь заданий - *действие необратимо*. **Используйте в экстренных случаях как одно из средств перехвата.** **Внимание!** Данная команда НЕ прерывает полёт коптера в уже указанную точку (например: элементы взлёта, посадки; следование до начальной точки анимации и т.д.)
* Кнопка `Emergency land` - Открывает диалоговое окно дополнительного модуля быстрого выбора коптера и его последующей экстренной посадки \ дизарма. *Смотреть далее.*
### Полётные функции (команды)
В данном разделе находятся команды, позволяющие напрямую управлять коптером(ами).
* Кнопка `Test leds` - Все выбранные коптеры выполняют двухсекундную анимацию (бегущие точки) светодиодной лентой (белым цветом). Команда *безопасна* и может быть использована для проверки работы светодиодных лент \ качества и задержки подключения к серверу \ определения соответствия коптера и его `Copter ID` в таблице.
* Кнопка `Takeoff` - Все выбранные коптеры **совершают взлёт**, после чего зависают над точкой взлёта. Аналогично `Start animation`, кнопка активна, *только* если все выбранные коптеры готовы. **Внимание!** Используйте осторожно, соблюдайте технику безопасности. Не применяйте во время выполнения других полётных функций!
* Кнопка `Flip` - Все выбранные коптеры **совершают флип (flip)** - переворот на 360 градусов вокруг одной из *горизонтальных* осей. **Внимание!** Используйте осторожно, соблюдайте технику безопасности. *Для исполнения флипа коптер должен иметь минимальную высоту >2м.* Не применяйте во время выполнения других полётных функций!
* Кнопка `Land` - ВСЕ коптеры прекращают выполнение своих полётных заданий, очищают очередь заданий и немедленно *переходят в режим посадки.* **Используйте в экстренных случаях как одно из средств перехвата.**
* Кнопка `Diarm` - ВСЕ коптеры прекращают выполнение своих полётных заданий, очищают очередь заданий и немедленно *отключают моторы (disarm).* ==Это может привести к падению и повреждению коптеров== **Используйте в крайних случаях как последнее из средств перехвата.**
## Таблица состояния коптеров (клиентов)
При первом подключении клиента к серверу в таблицу добавляется строка для отображения состояния клиента, содержащая начальные данные, переданные клиентом при подключении (`Copter ID`). Строки НЕ удаляются после зарегистрированного отключения клиента. Строки можно сортировать по возрастанию \ убыванию значений любого из столбцов (кликнув по заголовку столбца).
Ячейки таблицы подсвечиваются:
* жёлтым, если необходимое значение отсутствует
* красным, если значение (состояние) ячейки неудовлетворительно (согласно внутренним проверкам)
* зелёным, если значение (состояние) ячейки удовлетворительно (согласно внутренним проверкам)
### Столбцы таблицы
* `copter ID` - имя (идентификатор) клиента. Может быт сконфигурирован на стороне клиента. Отображается сразу при подключении клиента. Рядом с каждым ID коптера расположен чекбокс - коптеры, чей ID отмечен чекбоксом положительно (галочка), считаются *выбранными*.
* `animation ID` - внутреннее название файла анимации, подгруженного клиентом. Отображается после выполнения `selfcheck`. *Проверьте соответствие названий файлов анимаций у коптеров*
* `battery V` - абсолютное значение напряжения на аккумуляторе коптера (в Вольтах, по данным полётного контроллера). *Убедитесь, что напряжение не ниже порогового для вашего аккумулятора.* **При критически низком значении коптер считается не готовым** - блокируется возможность взлёта и старта анимации.
* `battery %` - относительное значение напряжения на аккумуляторе коптера. Значение рассчитывается по среднему напряжению (по данным полётного контроллера) на ячейку аккумулятора (банку). *Убедитесь, что уровень заряда перед вылетом не менее 30%* **При критически низком значении коптер считается не готовым** - блокируется возможность взлёта и старта анимации.
* `selfcheck` - Все дополнительные сообщения и ошибки при самодиагностике (*Смотреть далее.*). При успешном прохождении самодиагностики без ошибок выводится значение `OK`, ячейка подсвечивается зелёным цветом. **При наличии ошибок коптер считается не готовым** - блокируется возможность взлёта и старта анимации.
* `time delta` - Разница между временем на сервере и клиенте (в секундах). *При слишком больших значениях сигнализирует об отсутствии синхронизации времени между коптером и клиентом!* В это значение так же входит сетевая задержка.
# Дополнительные операции
## Selfcheck
..
## Emergency land
Модуль экстренной посадки/дизарма, предназначенный для быстрого поиска оператором визуально неисправного коптера методом бинарного поиска
### Интерфейс
* Зелёная кнопка `1` - ...
* Красная кнопка `2` - ...
* Кнопка `Land` - все коптеры в выбранной ('зелёной') группе совершат процедуру экстренной посадки (аналогично кнопке `Land` в панели инструментов).
* Кнопка `Disarm` - все коптеры в выбранной ('зелёной') группе *немедленно отключают моторы (disarm).* (аналогично кнопке `Disarm` в панели инструментов) ==Это может привести к падению и повреждению коптеров==.
### Алгоритм использования
* ...
<!--stackedit_data:
eyJoaXN0b3J5IjpbLTE1MDIyMDAwMjgsOTI1MDAyMDkwLC0xMz
IzMTk2MzMzLC01MDIzODYyNjMsLTI5NDk3MDcyOCw5MzY1NzEz
ODgsODcyNjgwNjE4XX0=
-->

57
docs/start-tutorial.md Normal file
View File

@@ -0,0 +1,57 @@
# Инструкция по настройке и запуску клиента и сервера
## Список оборудования
Данное ПО предназначено для управления несколькими квадракоптерами с компьютера-сервера. Для полноценной работы необходимо следующее оборудование:
* Один или несколько квадрокоптеров, работающих на базе ПО [Клевер](https://github.com/copterexpress/clever).
* Компьютер с операционной системой Linux.
* Wifi роутер, работающий на частоте 2.4 ГГц, либо 5.8 ГГц, если эту частоту поддерживают wifi модули коптеров и компьютера.
## Подготовка ПО
Скачайте на компьютер последний образ (CleverSwarm-XXX.img.zip) и исходный код (Source code) из последнего [релиза](https://github.com/artem30801/CleverSwarm/releases/latest). Разархивируйте исходный код в удобную директорию.
## Настройка роутера
Для управления одним или несколькими коптерами требуется подключение коптеров и сервера к одной сети. Для этого требуется отдельный wifi роутер с известным SSID и паролем. Подключите компьютер, который будет использоваться в качестве сервера, к сети роутера и узнайте его ip адрес - он понадобится для дальнейшей настройки.
## Настройка и запуск клиента
* Запишите образ на microSD карту, используя [Etcher](https://www.balena.io/etcher/).
* Вставьте флешку в Raspberry Pi, включите коптер. Дождитесь появления сети `CLEVERSHOW-XXXX`.
* Подключитесь к сети коптера, используя пароль `cleverwifi`.
* Настройте коптер, чтобы корректно работал режим позиции. По-умолчанию образ сконфигурирован для получения позиции с камеры с помощью aruco-маркеров и optical flow. Камера направлена вниз и вперёд, загружена тестовая карта меток. Если ваш способ позиционирования отличается - можно либо настроить данный образ, либо [собрать образ](image-building.md) со своими настройками.
* Перейдите в директорию клиента и запустите скрипт настройки клиента
```bash
cd ~/CleverSwarm/Drone
sudo ./client_setup.sh
```
* Выполните скрипт настройки клиента с указанными параметрами - SSID, пароль точки доступа, имя коптера, ip сервера.
* Коптер переключится в режим клиента указанной точки доступа и настроит автозапуск клиента copter_client.py
Документация по клиентской части находится [здесь](client.md).
## Настройка и запуск сервера
* Установите [chrony](https://chrony.tuxfamily.org/index.html) и Python 3 на ваш компьютер:
```bash
sudo apt install chrony python3 python3-pip
```
* Установите необходимые python-пакеты с помощью команды (запущенной из директории с исходным кодом)
```bash
pip3 install -r requirements.txt
```
* Подключитесь к wifi сети роутера, к которому подключены коптеры.
* Скопируйте [файл настроек chrony](../Server/chrony.conf) в `/etc/chrony/chrony.conf`. Если ip адрес сети начинается не с `192.168.`, то исправьте адрес после слова allow в скопированном файле настроек.
* Перезапустите сервис chrony
```bash
cd source-code-dir
sudo systemctl restart chrony
```
* Перейдите в директорию сервера из директории с исходным кодом и запустите сервер
```bash
cd source-code-dir/Server
python3 server_qt.py
```
Документация по серверной части находится [здесь](server.md).
<!--stackedit_data:
eyJoaXN0b3J5IjpbLTIwNjI5MzIwMTFdfQ==
-->

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env bash
pip3 install -r requirements.txt

2
install_requirements.bat Normal file
View File

@@ -0,0 +1,2 @@
pip3 install -r requirements.txt
pause

5
requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
indexed.py==0.0.1
numpy==1.16.4
PyQt5==5.13.0
PyQt5-sip==4.19.18
selectors2==2.0.1