Compare commits
226 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a91f9e5a6b | ||
|
|
6b74f75616 | ||
|
|
9cd9babb83 | ||
|
|
380c14da56 | ||
|
|
32c1c18af2 | ||
|
|
b7077339a1 | ||
|
|
52fd505ffc | ||
|
|
b911b7a3dd | ||
|
|
9b38d5135e | ||
|
|
0346c48546 | ||
|
|
212c6ca5ac | ||
|
|
361c89d016 | ||
|
|
b0cbc67799 | ||
|
|
18cfb08054 | ||
|
|
66121e6d5d | ||
|
|
a4841de17e | ||
|
|
84aef97e37 | ||
|
|
f14e1976e7 | ||
|
|
8c9acc98fb | ||
|
|
bd36428bd4 | ||
|
|
7f7276e34a | ||
|
|
460761ef68 | ||
|
|
9fefc6428e | ||
|
|
ffe4423c10 | ||
|
|
3f02919cd9 | ||
|
|
3075badf33 | ||
|
|
29d66ee264 | ||
|
|
846ec3a430 | ||
|
|
a65a50ffd3 | ||
|
|
c945bb9d03 | ||
|
|
3dc9575c9e | ||
|
|
f901167009 | ||
|
|
8519a6c6a4 | ||
|
|
d8fc2c088f | ||
|
|
679cd37ec6 | ||
|
|
40be77f9c3 | ||
|
|
1a0d762c4e | ||
|
|
a7e0032225 | ||
|
|
0b9df02faa | ||
|
|
0dcf7d1520 | ||
|
|
b146057ca7 | ||
|
|
2628d912cd | ||
|
|
2e75d531a6 | ||
|
|
be7607bc17 | ||
|
|
5bd724141e | ||
|
|
7b5538c104 | ||
|
|
462e7d6ffe | ||
|
|
68a32e036e | ||
|
|
dba5b63230 | ||
|
|
26efccb110 | ||
|
|
e0516663aa | ||
|
|
d737d91bc2 | ||
|
|
1b10d59188 | ||
|
|
a9ca13c55d | ||
|
|
57fbe9cc09 | ||
|
|
061b225690 | ||
|
|
27189d559a | ||
|
|
d9e0c94797 | ||
|
|
e2e719a12e | ||
|
|
52ec97f11f | ||
|
|
d094386c4d | ||
|
|
515777bd96 | ||
|
|
d56d0dfaaf | ||
|
|
60b22d0df8 | ||
|
|
0072bf8330 | ||
|
|
f36b4546a9 | ||
|
|
c0a97fd263 | ||
|
|
46932e4adf | ||
|
|
0c2675ec28 | ||
|
|
f4e5d7978a | ||
|
|
cc7ba566c5 | ||
|
|
d47e3f6bbf | ||
|
|
2335c74b55 | ||
|
|
ca4bab7bf8 | ||
|
|
d4d84ea422 | ||
|
|
f985e07be4 | ||
|
|
bab62cf374 | ||
|
|
4eb988c34d | ||
|
|
3006d7e32e | ||
|
|
3e9b7426bf | ||
|
|
e8f93782e0 | ||
|
|
839a8b4102 | ||
|
|
89a1b81ea2 | ||
|
|
6306af3c4a | ||
|
|
910b24c460 | ||
|
|
5dc580c76b | ||
|
|
c0705222f5 | ||
|
|
b51e8add55 | ||
|
|
873785bb53 | ||
|
|
86211df4b3 | ||
|
|
5ef5c6b641 | ||
|
|
5b96253146 | ||
|
|
54111504ac | ||
|
|
bc2a2895e0 | ||
|
|
80833486cd | ||
|
|
e92c59bf25 | ||
|
|
d511b2f65c | ||
|
|
921785c29d | ||
|
|
1db42fd268 | ||
|
|
6313cc8135 | ||
|
|
7abf7de71b | ||
|
|
c5628cecc0 | ||
|
|
5212d3bfe5 | ||
|
|
bed317a05b | ||
|
|
7644e29f71 | ||
|
|
3245d5fcf5 | ||
|
|
9dbce4d08b | ||
|
|
84a046ce82 | ||
|
|
d5c5a8723a | ||
|
|
67051b21a0 | ||
|
|
f646f003c1 | ||
|
|
ad1a3925ff | ||
|
|
5ae686ac5f | ||
|
|
6a1429c2eb | ||
|
|
4645c0bc13 | ||
|
|
51e5fa3b03 | ||
|
|
c3ff38a310 | ||
|
|
a547aacfa9 | ||
|
|
06fc6ceeb6 | ||
|
|
e8c3153ff5 | ||
|
|
84ac98cfe1 | ||
|
|
9579342e95 | ||
|
|
04ed4996e7 | ||
|
|
f22b4cd39a | ||
|
|
4d2ea5478d | ||
|
|
8ca722165a | ||
|
|
88a14a753e | ||
|
|
01bc387134 | ||
|
|
c692f4d393 | ||
|
|
fe9df2c34f | ||
|
|
a1968467de | ||
|
|
875698c926 | ||
|
|
f035d37243 | ||
|
|
3f7dec4d79 | ||
|
|
3d59c78ad8 | ||
|
|
d6b664e2f6 | ||
|
|
a387783287 | ||
|
|
a2d610133a | ||
|
|
62fb15e58f | ||
|
|
05c22bb839 | ||
|
|
08eae11f25 | ||
|
|
fb90bc8c1f | ||
|
|
0c275dce98 | ||
|
|
97b8eb435a | ||
|
|
7ebc57b659 | ||
|
|
2036e7e3ec | ||
|
|
bf054ef9f5 | ||
|
|
26e67edafe | ||
|
|
5ab31f5133 | ||
|
|
2ec6930099 | ||
|
|
4aa8f3f5dc | ||
|
|
2506c8420f | ||
|
|
90a04c04d2 | ||
|
|
48c800fd5a | ||
|
|
dafce44c0a | ||
|
|
a70885ad18 | ||
|
|
b5110d9df3 | ||
|
|
1391ffa2a5 | ||
|
|
ce8b52f684 | ||
|
|
0256deef66 | ||
|
|
f05a47d636 | ||
|
|
db4d84e1c0 | ||
|
|
adfde391ab | ||
|
|
512b4c2e09 | ||
|
|
e56143c508 | ||
|
|
f9aa441ce8 | ||
|
|
37e0185d25 | ||
|
|
29070efc06 | ||
|
|
3ddd37b843 | ||
|
|
409b603530 | ||
|
|
0458de2fb0 | ||
|
|
7c74ce3212 | ||
|
|
624162a3ec | ||
|
|
612b222887 | ||
|
|
d4efa58388 | ||
|
|
04ba342542 | ||
|
|
3105ce109c | ||
|
|
757b829a17 | ||
|
|
ea16ef847e | ||
|
|
294ccfe3f9 | ||
|
|
6ef5298b8b | ||
|
|
d31b41ed9e | ||
|
|
2100ca222c | ||
|
|
2dcb929089 | ||
|
|
7a95cbdf62 | ||
|
|
1ab251af6a | ||
|
|
d1c5e847da | ||
|
|
dc501bb2e6 | ||
|
|
e3e722a972 | ||
|
|
f7b47afae7 | ||
|
|
74d1eeca24 | ||
|
|
fa8f5ba41e | ||
|
|
1ee279bc5a | ||
|
|
d724f93e36 | ||
|
|
96783c1ee2 | ||
|
|
db5c6d0865 | ||
|
|
5f6df5569e | ||
|
|
661a121d6f | ||
|
|
25856e4675 | ||
|
|
cedf3f8364 | ||
|
|
0a66d3f3e3 | ||
|
|
10251c3c43 | ||
|
|
3cd9973fc1 | ||
|
|
2c68dd1a31 | ||
|
|
34cfb3548a | ||
|
|
fc3e98bdc6 | ||
|
|
bb6226f420 | ||
|
|
79c4d0d128 | ||
|
|
571c12061e | ||
|
|
786dd72b82 | ||
|
|
e53d318e22 | ||
|
|
2d0e06f22c | ||
|
|
f544c32d6d | ||
|
|
c56b1040d4 | ||
|
|
51e4cbef32 | ||
|
|
8a60e4e467 | ||
|
|
bb792206cc | ||
|
|
6448e6b928 | ||
|
|
7b7d53c97a | ||
|
|
431b97a011 | ||
|
|
5ccf4fac48 | ||
|
|
ba21ed7cf9 | ||
|
|
1db6453fd2 | ||
|
|
713b2fef0d | ||
|
|
05aa5cc8c1 | ||
|
|
9e5eec4e3b |
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
apps/ios/cleverrc/roslib.js linguist-vendored
|
||||
apps/ios/cleverrc/BinUtils.swift linguist-vendored
|
||||
4
.gitmodules
vendored
@@ -1,4 +0,0 @@
|
||||
[submodule "clever/static"]
|
||||
path = clever/static
|
||||
url = https://github.com/CopterExpress/clever-rc.git
|
||||
branch = build
|
||||
21
LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Copter Express
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
54
README.md
@@ -1,62 +1,68 @@
|
||||
Клевер
|
||||
======
|
||||
# CLEVER
|
||||
|
||||
<img src="assets/clever.jpg" align="right" width="300px" alt="Клевер">
|
||||
<img src="docs/assets/clever3.png" align="right" width="300px" alt="CLEVER drone">
|
||||
|
||||
«Клевер» — это учебный конструктор программируемого квадрокоптера, состоящего из популярных открытых компонентов, а также набор необходимой документации и библиотек для работы с ним.
|
||||
CLEVER is an educational programmable drone kit consisting of an unassembled quadcopter, open source software and documentation. The kit includes Pixhawk/Pixracer autopilot running PX4 firmware, Raspberry Pi 3 as companion computer, a camera for computer vision navigation as well as additional sensors and peripheral devices.
|
||||
|
||||
Набор включает в себя полетный контроллер PixHawk/PixRacer с полетным стеком PX4, Raspberry Pi 3 в качестве управлящего бортового компьютера, модуль камеры для реализации полетов с использованием компьютерного зрения, а также набор различных датчиков и другой периферии.
|
||||
Copter Express has implemented a large number of different autonomous drone projects using exactly the same platform: [automated pizza delivery](https://www.youtube.com/watch?v=hmkAoZOtF58) in Samara and Kazan, coffee delivery in Skolkovo Innovation Center, [autonomous quadcopter with charging station](https://www.youtube.com/watch?v=RjX6nUqw1mI) for site monitoring and security, winning drones on [Robokross-2016](https://www.youtube.com/watch?v=dGbDaz_VmYU) and [Robokross-2017](https://youtu.be/AQnd2CRczbQ) competitions and many others.
|
||||
|
||||
На базе точно такой же платформы были созданы многие «большие» проекты компании Copter Express, например, дроны для [пиар-акций по автономной доставке пиццы](https://www.youtube.com/watch?v=hmkAoZOtF58) (Самара, Казань); дрон-доставщик кофе в Сколково, мониторинговый дрон с зарядной станцией, дроны-победители на полевых испытаниях «[Робокросс-2016](https://www.youtube.com/watch?v=dGbDaz_VmYU)», «[Робокросс-2017](https://youtu.be/AQnd2CRczbQ)» и многие другие.
|
||||
**The main documentation in Russian is available on our Gitbook:**
|
||||
**https://copterexpress.gitbooks.io/clever/content/**
|
||||
|
||||
Для того, чтобы научиться собирать, настраивать, пилотировать и программировать автономный дрон «Клевер», воспользуйтесь этим учебником.
|
||||
Use it to learn how to assemble, configure, pilot and program autonomous CLEVER drone.
|
||||
|
||||
Основная документация
|
||||
---------------------
|
||||
## Preconfigured RPi 3 image
|
||||
|
||||
https://copterexpress.gitbooks.io/clever/content/
|
||||
Preconfigured image for Raspberry Pi 3 with installed and configured software, ready to fly, is available [here](https://copterexpress.gitbooks.io/clever/content/docs/microsd_images.html).
|
||||
|
||||
**Готовый образ ОС** для RPi 3 с предустановленным и преднастроенным ПО можно скачать [здесь](https://copterexpress.gitbooks.io/clever/content/docs/microsd_images.html).
|
||||
Image includes:
|
||||
|
||||
[Описание API](https://copterexpress.gitbooks.io/clever/content/docs/simple_offboard.html) для автономных полетов.
|
||||
* Raspbian Stretch
|
||||
* ROS Kinetic
|
||||
* Configured networking
|
||||
* OpenCV
|
||||
* mavros
|
||||
* CLEVER software bundle for autonomous drone control
|
||||
|
||||
Ручная установка
|
||||
---------
|
||||
API description in Russian for autonomous flights is available [here](https://copterexpress.gitbooks.io/clever/simple_offboard.html).
|
||||
|
||||
Склонировать репозиторий в папку `/home/pi/catkin_ws/src/clever` (**важно**):
|
||||
## Manual installation
|
||||
|
||||
Install ROS Kinetic according to the [documentation](http://wiki.ros.org/kinetic/Installation).
|
||||
|
||||
Clone repo to directory `/home/pi/catkin_ws/src/clever`:
|
||||
|
||||
```bash
|
||||
cd ~/catkin_ws/src
|
||||
git clone https://github.com/CopterExpress/clever_bundle.git clever
|
||||
git clone https://github.com/CopterExpress/clever.git clever
|
||||
```
|
||||
|
||||
Пересобрать ROS-пакеты:
|
||||
Build ROS packages:
|
||||
|
||||
```bash
|
||||
cd ~/catkin_ws
|
||||
catkin_make -j1
|
||||
```
|
||||
|
||||
Включить сервис roscore (если он не включен):
|
||||
Enable systemd service `roscore` (if not enabled):
|
||||
|
||||
```bash
|
||||
sudo systemctl enable /home/pi/catkin_ws/src/clever/deploy/roscore.service
|
||||
sudo systemctl start roscore
|
||||
```
|
||||
|
||||
Включить сервис clever:
|
||||
Enable systemd service `clever`:
|
||||
|
||||
```bash
|
||||
sudo systemctl enable /home/pi/catkin_ws/src/clever/deploy/clever.service
|
||||
sudo systemctl start clever
|
||||
```
|
||||
|
||||
Зависимости
|
||||
-----------
|
||||
### Dependencies
|
||||
|
||||
[ROS Kinetic](http://wiki.ros.org/kinetic).
|
||||
|
||||
Необходимые для работы ROS-пакеты:
|
||||
Necessary ROS packages:
|
||||
|
||||
* `opencv3`
|
||||
* `mavros`
|
||||
@@ -65,8 +71,6 @@ sudo systemctl start clever
|
||||
* `cv_camera`
|
||||
* `nodelet`
|
||||
* `dynamic_reconfigure`
|
||||
* `bondcpp`, ветка `master`
|
||||
* `bondcpp`, branch `master`
|
||||
* `roslint`
|
||||
* `rosserial`
|
||||
|
||||
TODO: внести в package.xml
|
||||
|
||||
44
SUMMARY.md
@@ -1,44 +0,0 @@
|
||||
# Summary
|
||||
|
||||
* [Введение](README.md)
|
||||
* [Сборка](docs/assemble.md)
|
||||
* [Первоначальная настройка](docs/setup.md)
|
||||
* [Полетные режимы](docs/modes.md)
|
||||
* [Raspberry Pi](docs/raspberry.md)
|
||||
* [Образ операционной системы на RPi](docs/microsd_images.md)
|
||||
* [Подключение Raspberry Pi к PixHawk](docs/connection.md)
|
||||
* [Подключение по Wi-Fi](docs/wifi.md)
|
||||
* [Работа с QGroundControl через Wi-Fi](docs/gcs_bridge.md)
|
||||
* [SSH-доступ](docs/ssh.md)
|
||||
* [Неисправности радиоаппаратуры](docs/radioerrors.md)
|
||||
* [Безопасность](docs/safety.md)
|
||||
* [Техника безопасности по пайке](docs/tb.md)
|
||||
* [Просмотр видеострима с камер](docs/web_video_server.md)
|
||||
* [Работа с ROS](docs/ros.md)
|
||||
* [MAVROS](docs/mavros.md)
|
||||
* [Автономный полет в OFFBOARD](docs/simple_offboard.md)
|
||||
* [Навигация по ArUco-маркерам](docs/aruco.md)
|
||||
* [Взаимодействие с Arduino](docs/arduino.md)
|
||||
* [Системы координат](docs/frames.md)
|
||||
* [Работа с камерой \(компьютерное зрение\)](docs/camera.md)
|
||||
* [Ориентация камеры](docs/camera_frame.md)
|
||||
* [Визуализация с помощью rviz](docs/rviz.md)
|
||||
* [Работа с SITL](docs/sitl.md)
|
||||
* [Подключение GPS](docs/gps.md)
|
||||
* [Использование 3G-модема](docs/3g.md)
|
||||
* [Примеры программ](primeri-programm.md)
|
||||
* Учебник
|
||||
* [Урок 1](docs/les1.md)
|
||||
* [Урок 2](docs/les2.md)
|
||||
* [Урок 7](docs/les7.md)
|
||||
* [Урок 8](docs/les8.md)
|
||||
* [Урок 9](docs/les9.md)
|
||||
* [Урок 11](docs/les11.md)
|
||||
* [Урок 13](docs/les13.md)
|
||||
* [Урок 15](docs/les15.md)
|
||||
* [Урок 16](docs/les16.md)
|
||||
* Другое
|
||||
* [CopterHack-2017](docs/copterhack2017.md)
|
||||
* [Прошивка ESC контроллеров с помощью Arduino](docs/esc_firmware.md)
|
||||
* [Полезные ссылки](docs/links.md)
|
||||
|
||||
17
apps/ios/.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# Xcode
|
||||
.DS_Store
|
||||
build/
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
!default.xcworkspace
|
||||
xcuserdata
|
||||
profile
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
Pods/
|
||||
14
apps/ios/Podfile
Normal file
@@ -0,0 +1,14 @@
|
||||
project 'cleverrc.xcodeproj/'
|
||||
|
||||
# Uncomment the next line to define a global platform for your project
|
||||
# platform :ios, '9.0'
|
||||
|
||||
target 'cleverrc' do
|
||||
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
|
||||
use_frameworks!
|
||||
|
||||
# Pods for cleverrc
|
||||
pod 'SwiftSocket', '~> 2.0'
|
||||
pod 'NotificationBannerSwift'
|
||||
|
||||
end
|
||||
21
apps/ios/Podfile.lock
Normal file
@@ -0,0 +1,21 @@
|
||||
PODS:
|
||||
- MarqueeLabel/Swift (3.1.4)
|
||||
- NotificationBannerSwift (1.5.4):
|
||||
- MarqueeLabel/Swift
|
||||
- SnapKit (~> 4.0)
|
||||
- SnapKit (4.0.0)
|
||||
- SwiftSocket (2.0.2)
|
||||
|
||||
DEPENDENCIES:
|
||||
- NotificationBannerSwift
|
||||
- SwiftSocket (~> 2.0)
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
MarqueeLabel: bf768455fe88d427f71476ebb23f9092b660f40b
|
||||
NotificationBannerSwift: 4f6666c8421dcf11be0812dd1093d932c15921af
|
||||
SnapKit: a42d492c16e80209130a3379f73596c3454b7694
|
||||
SwiftSocket: 6f4c9c63fbc5c1d61188936bb3c599fd546f40ae
|
||||
|
||||
PODFILE CHECKSUM: fd5199f69c3ee8c1fbc0dd582477d890c8b2a24f
|
||||
|
||||
COCOAPODS: 1.4.0
|
||||
10
apps/ios/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
iOS-приложение для управления Клевером
|
||||
--------------------------------------
|
||||
|
||||
Для установки зависимостей необходим [CocoaPods](https://cocoapods.org):
|
||||
|
||||
```bash
|
||||
pod install
|
||||
```
|
||||
|
||||
Для разработки и сборки откройте в XCode файл `cleverrc.xcworkspace`.
|
||||
446
apps/ios/cleverrc.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,446 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 48;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
7C0AB7AB202A744400BAED27 /* BinUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0AB7AA202A744400BAED27 /* BinUtils.swift */; };
|
||||
7C51654120139237004D1F4D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C51654020139237004D1F4D /* AppDelegate.swift */; };
|
||||
7C51654320139237004D1F4D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C51654220139237004D1F4D /* ViewController.swift */; };
|
||||
7C51654620139237004D1F4D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7C51654420139237004D1F4D /* Main.storyboard */; };
|
||||
7C51654820139237004D1F4D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7C51654720139237004D1F4D /* Assets.xcassets */; };
|
||||
7C51654B20139237004D1F4D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7C51654920139237004D1F4D /* LaunchScreen.storyboard */; };
|
||||
7C516553201526BA004D1F4D /* index.html in Resources */ = {isa = PBXBuildFile; fileRef = 7C516552201526BA004D1F4D /* index.html */; };
|
||||
7C51655520153180004D1F4D /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 7C51655420153180004D1F4D /* main.js */; };
|
||||
7C7DECC2203CBCC4000C1C51 /* roslib.js in Resources */ = {isa = PBXBuildFile; fileRef = 7C45DCE9203A75A2009C73F5 /* roslib.js */; };
|
||||
7CA401E22033CE17009FAA3B /* main.css in Resources */ = {isa = PBXBuildFile; fileRef = 7CA401E12033CE17009FAA3B /* main.css */; };
|
||||
7CA401E42033FA34009FAA3B /* telemetry.js in Resources */ = {isa = PBXBuildFile; fileRef = 7CA401E32033FA34009FAA3B /* telemetry.js */; };
|
||||
7CA401E6203471D9009FAA3B /* clever.svg in Resources */ = {isa = PBXBuildFile; fileRef = 7CA401E5203471D8009FAA3B /* clever.svg */; };
|
||||
C25141CAF1A7125F3CE29DDC /* Pods_cleverrc.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C38C04523251039FF13DDCD /* Pods_cleverrc.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
5C38C04523251039FF13DDCD /* Pods_cleverrc.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_cleverrc.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7C0AB7AA202A744400BAED27 /* BinUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BinUtils.swift; sourceTree = "<group>"; };
|
||||
7C45DCE9203A75A2009C73F5 /* roslib.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = roslib.js; sourceTree = "<group>"; };
|
||||
7C51653D20139237004D1F4D /* cleverrc.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = cleverrc.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7C51654020139237004D1F4D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7C51654220139237004D1F4D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
7C51654520139237004D1F4D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
7C51654720139237004D1F4D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
7C51654A20139237004D1F4D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
7C51654C20139237004D1F4D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
7C516552201526BA004D1F4D /* index.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = index.html; sourceTree = "<group>"; };
|
||||
7C51655420153180004D1F4D /* main.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = main.js; sourceTree = "<group>"; };
|
||||
7CA401E12033CE17009FAA3B /* main.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = main.css; sourceTree = "<group>"; };
|
||||
7CA401E32033FA34009FAA3B /* telemetry.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = telemetry.js; sourceTree = "<group>"; };
|
||||
7CA401E5203471D8009FAA3B /* clever.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = clever.svg; sourceTree = "<group>"; };
|
||||
AAC9195BF3A9BF6942EF4D0B /* Pods-cleverrc.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-cleverrc.release.xcconfig"; path = "Pods/Target Support Files/Pods-cleverrc/Pods-cleverrc.release.xcconfig"; sourceTree = "<group>"; };
|
||||
CB200F4B933204EA97E0E2E4 /* Pods-cleverrc.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-cleverrc.debug.xcconfig"; path = "Pods/Target Support Files/Pods-cleverrc/Pods-cleverrc.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
7C51653A20139237004D1F4D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C25141CAF1A7125F3CE29DDC /* Pods_cleverrc.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
4FA3968F2242239E15A656D2 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CB200F4B933204EA97E0E2E4 /* Pods-cleverrc.debug.xcconfig */,
|
||||
AAC9195BF3A9BF6942EF4D0B /* Pods-cleverrc.release.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
66C638F0021EBE07741B26F3 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C38C04523251039FF13DDCD /* Pods_cleverrc.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7C51653420139237004D1F4D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
66C638F0021EBE07741B26F3 /* Frameworks */,
|
||||
4FA3968F2242239E15A656D2 /* Pods */,
|
||||
7C51653E20139237004D1F4D /* Products */,
|
||||
7C51653F20139237004D1F4D /* cleverrc */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7C51653E20139237004D1F4D /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7C51653D20139237004D1F4D /* cleverrc.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7C51653F20139237004D1F4D /* cleverrc */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7C45DCE9203A75A2009C73F5 /* roslib.js */,
|
||||
7C51654020139237004D1F4D /* AppDelegate.swift */,
|
||||
7C51654720139237004D1F4D /* Assets.xcassets */,
|
||||
7C0AB7AA202A744400BAED27 /* BinUtils.swift */,
|
||||
7C51654C20139237004D1F4D /* Info.plist */,
|
||||
7C51654920139237004D1F4D /* LaunchScreen.storyboard */,
|
||||
7C51654420139237004D1F4D /* Main.storyboard */,
|
||||
7C51654220139237004D1F4D /* ViewController.swift */,
|
||||
7CA401E5203471D8009FAA3B /* clever.svg */,
|
||||
7C516552201526BA004D1F4D /* index.html */,
|
||||
7CA401E12033CE17009FAA3B /* main.css */,
|
||||
7C51655420153180004D1F4D /* main.js */,
|
||||
7CA401E32033FA34009FAA3B /* telemetry.js */,
|
||||
);
|
||||
path = cleverrc;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
7C51653C20139237004D1F4D /* cleverrc */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 7C51654F20139237004D1F4D /* Build configuration list for PBXNativeTarget "cleverrc" */;
|
||||
buildPhases = (
|
||||
9F096121C4A02BCE9D4FD1B9 /* [CP] Check Pods Manifest.lock */,
|
||||
7C51653920139237004D1F4D /* Sources */,
|
||||
7C51653A20139237004D1F4D /* Frameworks */,
|
||||
7C51653B20139237004D1F4D /* Resources */,
|
||||
A37DBBAD5E44E632F8A8A204 /* [CP] Embed Pods Frameworks */,
|
||||
9BAB41D26FC0095C7C86B9DE /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = cleverrc;
|
||||
productName = cleverrc;
|
||||
productReference = 7C51653D20139237004D1F4D /* cleverrc.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
7C51653520139237004D1F4D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 0920;
|
||||
ORGANIZATIONNAME = "Copter Express";
|
||||
TargetAttributes = {
|
||||
7C51653C20139237004D1F4D = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 7C51653820139237004D1F4D /* Build configuration list for PBXProject "cleverrc" */;
|
||||
compatibilityVersion = "Xcode 8.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 7C51653420139237004D1F4D;
|
||||
productRefGroup = 7C51653E20139237004D1F4D /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
7C51653C20139237004D1F4D /* cleverrc */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
7C51653B20139237004D1F4D /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7C51654B20139237004D1F4D /* LaunchScreen.storyboard in Resources */,
|
||||
7CA401E6203471D9009FAA3B /* clever.svg in Resources */,
|
||||
7CA401E42033FA34009FAA3B /* telemetry.js in Resources */,
|
||||
7C7DECC2203CBCC4000C1C51 /* roslib.js in Resources */,
|
||||
7C516553201526BA004D1F4D /* index.html in Resources */,
|
||||
7C51654820139237004D1F4D /* Assets.xcassets in Resources */,
|
||||
7CA401E22033CE17009FAA3B /* main.css in Resources */,
|
||||
7C51654620139237004D1F4D /* Main.storyboard in Resources */,
|
||||
7C51655520153180004D1F4D /* main.js in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
9BAB41D26FC0095C7C86B9DE /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-cleverrc/Pods-cleverrc-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
9F096121C4A02BCE9D4FD1B9 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-cleverrc-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
A37DBBAD5E44E632F8A8A204 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${SRCROOT}/Pods/Target Support Files/Pods-cleverrc/Pods-cleverrc-frameworks.sh",
|
||||
"${BUILT_PRODUCTS_DIR}/MarqueeLabel/MarqueeLabel.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/NotificationBannerSwift/NotificationBannerSwift.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/SwiftSocket/SwiftSocket.framework",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MarqueeLabel.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NotificationBannerSwift.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftSocket.framework",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-cleverrc/Pods-cleverrc-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
7C51653920139237004D1F4D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7C51654320139237004D1F4D /* ViewController.swift in Sources */,
|
||||
7C51654120139237004D1F4D /* AppDelegate.swift in Sources */,
|
||||
7C0AB7AB202A744400BAED27 /* BinUtils.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
7C51654420139237004D1F4D /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
7C51654520139237004D1F4D /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
path = .;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7C51654920139237004D1F4D /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
7C51654A20139237004D1F4D /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
path = .;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
7C51654D20139237004D1F4D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.2;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
7C51654E20139237004D1F4D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.2;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
7C51655020139237004D1F4D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = CB200F4B933204EA97E0E2E4 /* Pods-cleverrc.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 7QY6KJ2672;
|
||||
INFOPLIST_FILE = cleverrc/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = coex.cleverrc;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
7C51655120139237004D1F4D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = AAC9195BF3A9BF6942EF4D0B /* Pods-cleverrc.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 7QY6KJ2672;
|
||||
INFOPLIST_FILE = cleverrc/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = coex.cleverrc;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
7C51653820139237004D1F4D /* Build configuration list for PBXProject "cleverrc" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
7C51654D20139237004D1F4D /* Debug */,
|
||||
7C51654E20139237004D1F4D /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
7C51654F20139237004D1F4D /* Build configuration list for PBXNativeTarget "cleverrc" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
7C51655020139237004D1F4D /* Debug */,
|
||||
7C51655120139237004D1F4D /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 7C51653520139237004D1F4D /* Project object */;
|
||||
}
|
||||
7
apps/ios/cleverrc.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:cleverrc.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
10
apps/ios/cleverrc.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:cleverrc.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
46
apps/ios/cleverrc/AppDelegate.swift
Normal file
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// AppDelegate.swift
|
||||
// cleverrc
|
||||
//
|
||||
// Created by Oleg Kalachev on 20.01.2018.
|
||||
// Copyright © 2018 Copter Express. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationWillResignActive(_ application: UIApplication) {
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
|
||||
}
|
||||
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||
}
|
||||
|
||||
func applicationWillEnterForeground(_ application: UIApplication) {
|
||||
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "cleverios180.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "cleverios180-1.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "83.5x83.5",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"size" : "1024x1024",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
6
apps/ios/cleverrc/Assets.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
20
apps/ios/cleverrc/Assets.xcassets/Image.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
29
apps/ios/cleverrc/Base.lproj/LaunchScreen.storyboard
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.10586584359407425" green="0.10589186102151871" blue="0.10586420446634293" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
46
apps/ios/cleverrc/Base.lproj/Main.storyboard
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="retina4_7" orientation="landscape">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="cleverrc" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="667" height="375"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<wkWebView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dtJ-LN-BYT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="667" height="375"/>
|
||||
<color key="backgroundColor" red="0.12939286231994629" green="0.12942266464233398" blue="0.12939092516899109" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<wkWebViewConfiguration key="configuration">
|
||||
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
|
||||
<wkPreferences key="preferences"/>
|
||||
</wkWebViewConfiguration>
|
||||
</wkWebView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.1097869947552681" green="0.10981365293264389" blue="0.10978532582521439" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="dtJ-LN-BYT" firstAttribute="height" secondItem="8bC-Xf-vdC" secondAttribute="height" id="KRn-ag-67x"/>
|
||||
<constraint firstItem="dtJ-LN-BYT" firstAttribute="width" secondItem="8bC-Xf-vdC" secondAttribute="width" id="qOr-gg-e4M"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="webView" destination="dtJ-LN-BYT" id="bQ3-jy-o6E"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="138.0809595202399" y="124"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
453
apps/ios/cleverrc/BinUtils.swift
vendored
Normal file
@@ -0,0 +1,453 @@
|
||||
//
|
||||
// BinUtils.swift
|
||||
// BinUtils
|
||||
//
|
||||
// Created by Nicolas Seriot on 12/03/16.
|
||||
// Copyright © 2016 Nicolas Seriot. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreFoundation
|
||||
|
||||
// MARK: protocol UnpackedType
|
||||
|
||||
public protocol Unpackable {}
|
||||
|
||||
extension NSString: Unpackable {}
|
||||
extension Bool: Unpackable {}
|
||||
extension Int: Unpackable {}
|
||||
extension Double: Unpackable {}
|
||||
|
||||
// MARK: protocol DataConvertible
|
||||
|
||||
protocol DataConvertible {}
|
||||
|
||||
extension DataConvertible {
|
||||
|
||||
init?(data: Data) {
|
||||
guard data.count == MemoryLayout<Self>.size else { return nil }
|
||||
self = data.withUnsafeBytes { $0.pointee }
|
||||
}
|
||||
|
||||
init?(bytes: [UInt8]) {
|
||||
let data = Data(bytes:bytes)
|
||||
self.init(data:data)
|
||||
}
|
||||
|
||||
var data: Data {
|
||||
var value = self
|
||||
return Data(buffer: UnsafeBufferPointer(start: &value, count: 1))
|
||||
}
|
||||
}
|
||||
|
||||
extension Bool : DataConvertible { }
|
||||
|
||||
extension Int8 : DataConvertible { }
|
||||
extension Int16 : DataConvertible { }
|
||||
extension Int32 : DataConvertible { }
|
||||
extension Int64 : DataConvertible { }
|
||||
|
||||
extension UInt8 : DataConvertible { }
|
||||
extension UInt16 : DataConvertible { }
|
||||
extension UInt32 : DataConvertible { }
|
||||
extension UInt64 : DataConvertible { }
|
||||
|
||||
extension Float32 : DataConvertible { }
|
||||
extension Float64 : DataConvertible { }
|
||||
|
||||
// MARK: String extension
|
||||
|
||||
extension String {
|
||||
subscript (from:Int, to:Int) -> String {
|
||||
return NSString(string: self).substring(with: NSMakeRange(from, to-from))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Data extension
|
||||
|
||||
extension Data {
|
||||
var bytes : [UInt8] {
|
||||
return self.withUnsafeBytes {
|
||||
[UInt8](UnsafeBufferPointer(start: $0, count: self.count))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: functions
|
||||
|
||||
public func hexlify(_ data:Data) -> String {
|
||||
|
||||
// similar to hexlify() in Python's binascii module
|
||||
// https://docs.python.org/2/library/binascii.html
|
||||
|
||||
var s = String()
|
||||
var byte: UInt8 = 0
|
||||
|
||||
for i in 0 ..< data.count {
|
||||
NSData(data: data).getBytes(&byte, range: NSMakeRange(i, 1))
|
||||
s = s.appendingFormat("%02x", byte)
|
||||
}
|
||||
|
||||
return s as String
|
||||
}
|
||||
|
||||
public func unhexlify(_ string:String) -> Data? {
|
||||
|
||||
// similar to unhexlify() in Python's binascii module
|
||||
// https://docs.python.org/2/library/binascii.html
|
||||
|
||||
let s = string.uppercased().replacingOccurrences(of: " ", with: "")
|
||||
|
||||
let nonHexCharacterSet = CharacterSet(charactersIn: "0123456789ABCDEF").inverted
|
||||
if let range = s.rangeOfCharacter(from: nonHexCharacterSet) {
|
||||
print("-- found non hex character at range \(range)")
|
||||
return nil
|
||||
}
|
||||
|
||||
var data = Data(capacity: s.count / 2)
|
||||
|
||||
for i in stride(from: 0, to:s.count, by:2) {
|
||||
let byteString = s[i, i+2]
|
||||
let byte = UInt8(byteString.withCString { strtoul($0, nil, 16) })
|
||||
data.append([byte] as [UInt8], count: 1)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func readIntegerType<T:DataConvertible>(_ type:T.Type, bytes:[UInt8], loc:inout Int) -> T {
|
||||
let size = MemoryLayout<T>.size
|
||||
let sub = Array(bytes[loc..<(loc+size)])
|
||||
loc += size
|
||||
return T(bytes: sub)!
|
||||
}
|
||||
|
||||
func readFloatingPointType<T:DataConvertible>(_ type:T.Type, bytes:[UInt8], loc:inout Int, isBigEndian:Bool) -> T {
|
||||
let size = MemoryLayout<T>.size
|
||||
let sub = Array(bytes[loc..<(loc+size)])
|
||||
loc += size
|
||||
let sub_ = isBigEndian ? sub.reversed() : sub
|
||||
return T(bytes: sub_)!
|
||||
}
|
||||
|
||||
func isBigEndianFromMandatoryByteOrderFirstCharacter(_ format:String) -> Bool {
|
||||
|
||||
guard let firstChar = format.first else { assertionFailure("empty format"); return false }
|
||||
|
||||
let s = NSString(string: String(firstChar))
|
||||
let c = s.substring(to: 1)
|
||||
|
||||
if c == "@" { assertionFailure("native size and alignment is unsupported") }
|
||||
|
||||
if c == "=" || c == "<" { return false }
|
||||
if c == ">" || c == "!" { return true }
|
||||
|
||||
assertionFailure("format '\(format)' first character must be among '=<>!'")
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// akin to struct.calcsize(fmt)
|
||||
func numberOfBytesInFormat(_ format:String) -> Int {
|
||||
|
||||
var numberOfBytes = 0
|
||||
|
||||
var n = 0 // repeat counter
|
||||
|
||||
var mutableFormat = format
|
||||
|
||||
while !mutableFormat.isEmpty {
|
||||
|
||||
let c = mutableFormat.remove(at: mutableFormat.startIndex)
|
||||
|
||||
if let i = Int(String(c)) , 0...9 ~= i {
|
||||
if n > 0 { n *= 10 }
|
||||
n += i
|
||||
continue
|
||||
}
|
||||
|
||||
if c == "s" {
|
||||
numberOfBytes += max(n,1)
|
||||
n = 0
|
||||
continue
|
||||
}
|
||||
|
||||
for _ in 0..<max(n,1) {
|
||||
|
||||
switch(c) {
|
||||
|
||||
case "@", "<", "=", ">", "!", " ":
|
||||
()
|
||||
case "c", "b", "B", "x", "?":
|
||||
numberOfBytes += 1
|
||||
case "h", "H":
|
||||
numberOfBytes += 2
|
||||
case "i", "l", "I", "L", "f":
|
||||
numberOfBytes += 4
|
||||
case "q", "Q", "d":
|
||||
numberOfBytes += 8
|
||||
case "P":
|
||||
numberOfBytes += MemoryLayout<Int>.size
|
||||
default:
|
||||
assertionFailure("-- unsupported format \(c)")
|
||||
}
|
||||
}
|
||||
|
||||
n = 0
|
||||
}
|
||||
|
||||
return numberOfBytes
|
||||
}
|
||||
|
||||
func formatDoesMatchDataLength(_ format:String, data:Data) -> Bool {
|
||||
let sizeAccordingToFormat = numberOfBytesInFormat(format)
|
||||
let dataLength = data.count
|
||||
if sizeAccordingToFormat != dataLength {
|
||||
print("format \"\(format)\" expects \(sizeAccordingToFormat) bytes but data is \(dataLength) bytes")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/*
|
||||
pack() and unpack() should behave as Python's struct module https://docs.python.org/2/library/struct.html BUT:
|
||||
- native size and alignment '@' is not supported
|
||||
- as a consequence, the byte order specifier character is mandatory and must be among "=<>!"
|
||||
- native byte order '=' assumes a little-endian system (eg. Intel x86)
|
||||
- Pascal strings 'p' and native pointers 'P' are not supported
|
||||
*/
|
||||
|
||||
public enum BinUtilsError: Error {
|
||||
case formatDoesMatchDataLength(format:String, dataSize:Int)
|
||||
case unsupportedFormat(character:Character)
|
||||
}
|
||||
|
||||
public func pack(_ format:String, _ objects:[Any], _ stringEncoding:String.Encoding=String.Encoding.windowsCP1252) -> Data {
|
||||
|
||||
var objectsQueue = objects
|
||||
|
||||
var mutableFormat = format
|
||||
|
||||
var mutableData = Data()
|
||||
|
||||
var isBigEndian = false
|
||||
|
||||
let firstCharacter = mutableFormat.remove(at: mutableFormat.startIndex)
|
||||
|
||||
switch(firstCharacter) {
|
||||
case "<", "=":
|
||||
isBigEndian = false
|
||||
case ">", "!":
|
||||
isBigEndian = true
|
||||
case "@":
|
||||
assertionFailure("native size and alignment '@' is unsupported'")
|
||||
default:
|
||||
assertionFailure("unsupported format chacracter'")
|
||||
}
|
||||
|
||||
var n = 0 // repeat counter
|
||||
|
||||
while !mutableFormat.isEmpty {
|
||||
|
||||
let c = mutableFormat.remove(at: mutableFormat.startIndex)
|
||||
|
||||
if let i = Int(String(c)) , 0...9 ~= i {
|
||||
if n > 0 { n *= 10 }
|
||||
n += i
|
||||
continue
|
||||
}
|
||||
|
||||
var o : Any = 0
|
||||
|
||||
if c == "s" {
|
||||
o = objectsQueue.remove(at: 0)
|
||||
|
||||
guard let stringData = (o as! String).data(using: .utf8) else { assertionFailure(); return Data() }
|
||||
var bytes = stringData.bytes
|
||||
|
||||
let expectedSize = max(1, n)
|
||||
|
||||
// pad ...
|
||||
while bytes.count < expectedSize { bytes.append(0x00) }
|
||||
|
||||
// ... or trunk
|
||||
if bytes.count > expectedSize { bytes = Array(bytes[0..<expectedSize]) }
|
||||
|
||||
assert(bytes.count == expectedSize)
|
||||
|
||||
if isBigEndian { bytes = bytes.reversed() }
|
||||
|
||||
mutableData.append(bytes, count: bytes.count)
|
||||
|
||||
n = 0
|
||||
continue
|
||||
}
|
||||
|
||||
for _ in 0..<max(n,1) {
|
||||
|
||||
var bytes : [UInt8] = []
|
||||
|
||||
if c != "x" {
|
||||
o = objectsQueue.removeFirst()
|
||||
}
|
||||
|
||||
switch(c) {
|
||||
case "?":
|
||||
bytes = (o as! Bool) ? [0x01] : [0x00]
|
||||
case "c":
|
||||
let charAsString = (o as! NSString).substring(to: 1)
|
||||
guard let data = charAsString.data(using: stringEncoding) else {
|
||||
assertionFailure("cannot decode character \(charAsString) using encoding \(stringEncoding)")
|
||||
return Data()
|
||||
}
|
||||
bytes = data.bytes
|
||||
case "b":
|
||||
bytes = Int8(truncatingIfNeeded:o as! Int).data.bytes
|
||||
case "h":
|
||||
bytes = Int16(truncatingIfNeeded:o as! Int).data.bytes
|
||||
case "i", "l":
|
||||
bytes = Int32(truncatingIfNeeded:o as! Int).data.bytes
|
||||
case "q", "Q":
|
||||
bytes = Int64(o as! Int).data.bytes
|
||||
case "B":
|
||||
bytes = UInt8(truncatingIfNeeded:o as! Int).data.bytes
|
||||
case "H":
|
||||
bytes = UInt16(truncatingIfNeeded:o as! Int).data.bytes
|
||||
case "I", "L":
|
||||
bytes = UInt32(truncatingIfNeeded:o as! Int).data.bytes
|
||||
case "f":
|
||||
bytes = Float32(o as! Double).data.bytes
|
||||
case "d":
|
||||
bytes = Float64(o as! Double).data.bytes
|
||||
case "x":
|
||||
bytes = [0x00]
|
||||
default:
|
||||
assertionFailure("Unsupported packing format: \(c)")
|
||||
}
|
||||
|
||||
if isBigEndian { bytes = bytes.reversed() }
|
||||
let data = Data(bytes)
|
||||
mutableData.append(data)
|
||||
}
|
||||
|
||||
n = 0
|
||||
}
|
||||
|
||||
return mutableData
|
||||
}
|
||||
|
||||
public func unpack(_ format:String, _ data:Data, _ stringEncoding:String.Encoding=String.Encoding.windowsCP1252) throws -> [Unpackable] {
|
||||
|
||||
assert(CFByteOrderGetCurrent() == 1 /* CFByteOrderLittleEndian */, "\(#file) assumes little endian, but host is big endian")
|
||||
|
||||
let isBigEndian = isBigEndianFromMandatoryByteOrderFirstCharacter(format)
|
||||
|
||||
if formatDoesMatchDataLength(format, data: data) == false {
|
||||
throw BinUtilsError.formatDoesMatchDataLength(format:format, dataSize:data.count)
|
||||
}
|
||||
|
||||
var a : [Unpackable] = []
|
||||
|
||||
var loc = 0
|
||||
|
||||
let bytes = data.bytes
|
||||
|
||||
var n = 0 // repeat counter
|
||||
|
||||
var mutableFormat = format
|
||||
|
||||
mutableFormat.remove(at: mutableFormat.startIndex) // consume byte-order specifier
|
||||
|
||||
while !mutableFormat.isEmpty {
|
||||
|
||||
let c = mutableFormat.remove(at: mutableFormat.startIndex)
|
||||
|
||||
if let i = Int(String(c)) , 0...9 ~= i {
|
||||
if n > 0 { n *= 10 }
|
||||
n += i
|
||||
continue
|
||||
}
|
||||
|
||||
if c == "s" {
|
||||
let length = max(n,1)
|
||||
let sub = Array(bytes[loc..<loc+length])
|
||||
|
||||
guard let s = NSString(bytes: sub, length: length, encoding: stringEncoding.rawValue) else {
|
||||
assertionFailure("-- not a string: \(sub)")
|
||||
return []
|
||||
}
|
||||
|
||||
a.append(s)
|
||||
|
||||
loc += length
|
||||
|
||||
n = 0
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
for _ in 0..<max(n,1) {
|
||||
|
||||
var o : Unpackable?
|
||||
|
||||
switch(c) {
|
||||
|
||||
case "c":
|
||||
let optionalString = NSString(bytes: [bytes[loc]], length: 1, encoding: String.Encoding.utf8.rawValue)
|
||||
loc += 1
|
||||
guard let s = optionalString else { assertionFailure(); return [] }
|
||||
o = s
|
||||
case "b":
|
||||
let r = readIntegerType(Int8.self, bytes:bytes, loc:&loc)
|
||||
o = Int(r)
|
||||
case "B":
|
||||
let r = readIntegerType(UInt8.self, bytes:bytes, loc:&loc)
|
||||
o = Int(r)
|
||||
case "?":
|
||||
let r = readIntegerType(Bool.self, bytes:bytes, loc:&loc)
|
||||
o = r ? true : false
|
||||
case "h":
|
||||
let r = readIntegerType(Int16.self, bytes:bytes, loc:&loc)
|
||||
o = Int(isBigEndian ? Int16(bigEndian: r) : r)
|
||||
case "H":
|
||||
let r = readIntegerType(UInt16.self, bytes:bytes, loc:&loc)
|
||||
o = Int(isBigEndian ? UInt16(bigEndian: r) : r)
|
||||
case "i":
|
||||
fallthrough
|
||||
case "l":
|
||||
let r = readIntegerType(Int32.self, bytes:bytes, loc:&loc)
|
||||
o = Int(isBigEndian ? Int32(bigEndian: r) : r)
|
||||
case "I":
|
||||
fallthrough
|
||||
case "L":
|
||||
let r = readIntegerType(UInt32.self, bytes:bytes, loc:&loc)
|
||||
o = Int(isBigEndian ? UInt32(bigEndian: r) : r)
|
||||
case "q":
|
||||
let r = readIntegerType(Int64.self, bytes:bytes, loc:&loc)
|
||||
o = Int(isBigEndian ? Int64(bigEndian: r) : r)
|
||||
case "Q":
|
||||
let r = readIntegerType(UInt64.self, bytes:bytes, loc:&loc)
|
||||
o = Int(isBigEndian ? UInt64(bigEndian: r) : r)
|
||||
case "f":
|
||||
let r = readFloatingPointType(Float32.self, bytes:bytes, loc:&loc, isBigEndian:isBigEndian)
|
||||
o = Double(r)
|
||||
case "d":
|
||||
let r = readFloatingPointType(Float64.self, bytes:bytes, loc:&loc, isBigEndian:isBigEndian)
|
||||
o = Double(r)
|
||||
case "x":
|
||||
loc += 1
|
||||
case " ":
|
||||
()
|
||||
default:
|
||||
throw BinUtilsError.unsupportedFormat(character:c)
|
||||
}
|
||||
|
||||
if let o = o { a.append(o) }
|
||||
}
|
||||
|
||||
n = 0
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
46
apps/ios/cleverrc/Info.plist
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Clever RC</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
10
apps/ios/cleverrc/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
iOS-приложение для управления Клевером
|
||||
--------------------------------------
|
||||
|
||||
Для установки зависимостей необходим [CocoaPods](https://cocoapods.org):
|
||||
|
||||
```bash
|
||||
pod install
|
||||
```
|
||||
|
||||
Для разработки и сборки откройте в XCode файл `cleverrc.xcworkspace`.
|
||||
74
apps/ios/cleverrc/ViewController.swift
Normal file
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// ViewController.swift
|
||||
// cleverrc
|
||||
//
|
||||
// Created by Oleg Kalachev on 20.01.2018.
|
||||
// Copyright © 2018 Copter Express. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import WebKit
|
||||
import SwiftSocket
|
||||
import NotificationBannerSwift
|
||||
|
||||
class ViewController: UIViewController, WKScriptMessageHandler {
|
||||
@IBOutlet weak var webView: WKWebView!
|
||||
let impactGenerator = UIImpactFeedbackGenerator(style: .medium)
|
||||
let notificationGenerator = UINotificationFeedbackGenerator()
|
||||
let udpSocket = UDPClient(address:"255.255.255.255", port: 35602)
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Don't lock screen
|
||||
UIApplication.shared.isIdleTimerDisabled = true
|
||||
|
||||
// Setup webview event handlers
|
||||
webView.configuration.userContentController.add(self, name: "control")
|
||||
webView.configuration.userContentController.add(self, name: "controlStart")
|
||||
webView.configuration.userContentController.add(self, name: "lowBattery")
|
||||
webView.configuration.userContentController.add(self, name: "notification")
|
||||
|
||||
// Load the main page
|
||||
let url = Bundle.main.url(forResource: "index", withExtension: "html")
|
||||
let requestObj = URLRequest(url: url!)
|
||||
webView.load(requestObj)
|
||||
|
||||
// Setup UDP broadcasting
|
||||
udpSocket.enableBroadcast()
|
||||
|
||||
// Set UDP broadcasting interface
|
||||
var wifiInterface = if_nametoindex("en0");
|
||||
setsockopt(udpSocket.fd!, IPPROTO_IP, IP_BOUND_IF, &wifiInterface, socklen_t(MemoryLayout<UInt32>.size));
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning() {
|
||||
super.didReceiveMemoryWarning()
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
||||
if (message.name == "control") {
|
||||
// Send UDP control message
|
||||
let m = message.body as! NSDictionary;
|
||||
let d = pack("<hhhh", [m["x"]!, m["y"]!, m["z"]!, m["r"]!])
|
||||
_ = udpSocket.send(data: d)
|
||||
} else if (message.name == "lowBattery") {
|
||||
// Got low battery notification
|
||||
print("Low battery notification")
|
||||
notificationGenerator.notificationOccurred(.warning)
|
||||
} else if (message.name == "notification") {
|
||||
// Got notification message
|
||||
print(message)
|
||||
let m = message.body as! NSDictionary;
|
||||
let level = m["level"] as! Int
|
||||
if level == 4 {
|
||||
let banner = NotificationBanner(title: m["msg"] as! String, style: .warning)
|
||||
banner.show()
|
||||
} else {
|
||||
let banner = NotificationBanner(title: m["msg"] as! String, style: .danger)
|
||||
banner.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
84
apps/ios/cleverrc/clever.svg
Normal file
@@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 52.5 52.5" enable-background="new 0 0 52.5 52.5" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="white" fill-opacity="0.5" d="M35.7,17.2c0.3-0.3,0.5-0.7,0.7-1.1c0.2-0.4,0.3-0.9,0.3-1.4c0-0.5-0.1-1-0.3-1.4c-0.2-0.5-0.5-0.9-0.8-1.3
|
||||
c-0.4-0.4-0.9-0.7-1.4-0.9c-0.6-0.2-1.2-0.3-1.8-0.2c-0.5,0.1-0.9,0.2-1.3,0.4c-0.2,0.1-0.4,0.3-0.6,0.4L30.2,12
|
||||
c-0.1,0.1-0.1,0.1-0.2,0.1c-0.1,0.1-0.3,0.2-0.4,0.2c-0.1,0.1-0.3,0.1-0.4,0.2c-0.1,0-0.2,0.1-0.4,0.1c-0.1,0-0.2,0-0.3,0
|
||||
c-0.6,0.1-1.3-0.1-1.9-0.4c-0.2-0.1-0.4-0.2-0.6-0.4c0,0-0.1-0.1-0.1-0.1c0,0-0.1-0.1-0.1-0.1c0,0,0,0-0.1-0.1
|
||||
c-0.1-0.1-0.1-0.2-0.1-0.3c0,0,0,0,0-0.1l0-6.5c-0.5-0.6-1.1-1.1-1.7-1.6c-0.9-0.7-1.9-1.4-3-1.8c-0.9-0.4-1.8-0.7-2.7-1
|
||||
c-0.9-0.2-1.7-0.3-2.6-0.4c-0.9-0.1-1.8,0-2.7,0.1c-0.7,0.1-1.5,0.2-2.2,0.5C9.4,0.9,8.1,1.5,7,2.2C6.2,2.7,5.4,3.3,4.7,3.9
|
||||
C4,4.6,3.4,5.3,2.8,6c-0.5,0.7-1,1.4-1.3,2.2C1.1,9,0.8,9.8,0.6,10.6c-0.2,0.7-0.4,1.4-0.4,2.1C0,13.7,0,14.6,0,15.6
|
||||
c0,0.8,0.2,1.7,0.4,2.5c0.2,0.9,0.5,1.7,0.8,2.5C1.8,21.8,2.5,23,3.4,24c0.3,0.4,0.7,0.8,1,1.1c0.2,0.2,0.3,0.3,0.5,0.5H11
|
||||
c0.1-0.1,0.1-0.2,0.1-0.2c0-0.1,0.1-0.1,0.1-0.2c0-0.1,0.1-0.1,0.1-0.2c0.1-0.4,0.2-0.8,0.1-1.1c0-0.1,0-0.1,0-0.2
|
||||
c0-0.1-0.1-0.1-0.1-0.2c0-0.1-0.1-0.2-0.2-0.2c0,0-0.1-0.1-0.1-0.1l0,0c0,0,0-0.1-0.1-0.1c0,0,0,0,0,0l-0.1-0.1
|
||||
c-0.1-0.1-0.2-0.3-0.3-0.4c-0.2-0.3-0.4-0.7-0.5-1c-0.1-0.3-0.2-0.7-0.3-1c-0.1-0.6-0.1-1.1,0-1.7c0.1-0.5,0.3-1,0.5-1.5
|
||||
c0.3-0.5,0.6-0.9,1-1.3c0.4-0.4,0.9-0.7,1.4-0.9c0.6-0.2,1.2-0.4,1.8-0.4c0.7,0,1.3,0.1,1.9,0.3c0.7,0.2,1.3,0.6,1.8,1.1
|
||||
c0.5,0.5,1,1.1,1.3,1.8c0.2,0.4,0.3,0.7,0.3,1.1c0.1,0.4,0.1,0.8,0.1,1.3c0,0.4-0.1,0.8-0.2,1.1c-0.1,0.3-0.3,0.7-0.4,1
|
||||
c-0.1,0.2-0.2,0.3-0.3,0.5c0,0.1-0.1,0.1-0.1,0.2l0,0l0,0l0,0.1c0,0,0,0.1-0.1,0.1l-0.1,0.1c0,0-0.1,0.1-0.1,0.1
|
||||
c-0.1,0.1-0.1,0.1-0.1,0.2c0,0.1-0.1,0.1-0.1,0.2c0,0.1,0,0.1,0,0.2c-0.1,0.3-0.1,0.7,0.1,1.1c0,0.1,0,0.1,0.1,0.2
|
||||
c0,0.1,0.1,0.1,0.1,0.2c0,0.1,0.1,0.2,0.2,0.3h7.2l0-7.5c0,0,0,0,0-0.1c0-0.1,0-0.1,0.1-0.2c0-0.1,0.1-0.1,0.1-0.2
|
||||
c0,0,0.1-0.1,0.1-0.1l0.1,0c0.1-0.1,0.2-0.1,0.3-0.2c0.5-0.3,1-0.5,1.6-0.6c0.2,0,0.3,0,0.5,0c0,0,0.1,0,0.1,0c0.1,0,0.1,0,0.2,0
|
||||
c0.1,0,0.2,0,0.4,0.1c0.3,0.1,0.7,0.2,0.9,0.4c0.1,0.1,0.2,0.1,0.3,0.2c0,0,0.1,0.1,0.1,0.1l0,0l-0.1,0.1c0,0,0,0,0,0l0.1-0.1
|
||||
l0.2,0.2c0.1,0.1,0.2,0.2,0.4,0.2c0.2,0.1,0.5,0.3,0.8,0.4c0.7,0.2,1.4,0.3,2,0.1c0.4-0.1,0.7-0.2,1.1-0.4
|
||||
C35.1,17.7,35.4,17.5,35.7,17.2z"/>
|
||||
<g>
|
||||
<polygon points="41.6,29.7 41.6,29.7 41.6,29.6 41.6,29.6 "/>
|
||||
<path fill="white" fill-opacity="0.5" d="M28.6,49.2c0.9,0.8,2,1.4,3,1.9c0.8,0.4,1.7,0.7,2.6,0.9c0.9,0.2,1.8,0.4,2.7,0.4c0.9,0.1,1.9,0,2.8-0.1
|
||||
c0.7-0.1,1.4-0.2,2.1-0.4c1.3-0.4,2.5-0.9,3.6-1.6c0.8-0.5,1.6-1.1,2.3-1.7c0.7-0.7,1.4-1.4,2-2.2c0.5-0.6,0.9-1.3,1.2-2
|
||||
c0.4-0.8,0.7-1.7,1-2.5c0.2-0.6,0.3-1.3,0.4-1.9c0.1-1,0.2-2,0.1-3c0-0.8-0.2-1.6-0.3-2.4c-0.2-0.9-0.5-1.7-0.8-2.6
|
||||
c-0.5-1.2-1.2-2.4-2.1-3.4c-0.3-0.4-0.6-0.8-1-1.1l-0.5-0.5h-6.1l-0.2,0.3c0,0.1-0.1,0.1-0.1,0.2c0,0.1-0.1,0.1-0.1,0.2
|
||||
c-0.1,0.4-0.2,0.8-0.1,1.1c0,0.1,0,0.1,0,0.2c0,0.1,0.1,0.1,0.1,0.2c0,0.1,0.1,0.2,0.2,0.3c0,0,0.1,0.1,0.1,0.1l0,0
|
||||
c0,0,0,0.1,0.1,0.1l0,0.1l0.1-0.1l0,0l0,0l0,0l0,0l0,0l0.1,0.1c0.2,0.2,0.3,0.4,0.5,0.7c0.3,0.5,0.5,1,0.6,1.5
|
||||
c0.1,0.7,0.1,1.5,0,2.2c-0.2,0.6-0.4,1.2-0.8,1.8c-0.3,0.4-0.7,0.8-1.1,1.1c-0.4,0.3-0.8,0.5-1.3,0.7c-0.5,0.2-1,0.3-1.5,0.3
|
||||
c-0.5,0-0.9,0-1.3-0.1c-0.5-0.1-1-0.3-1.4-0.6c-0.5-0.3-1-0.7-1.3-1.1c-0.4-0.5-0.7-1-0.9-1.6c-0.2-0.6-0.3-1.3-0.2-2
|
||||
c0-0.4,0.1-0.7,0.2-1c0.1-0.4,0.3-0.8,0.5-1.1c0.1-0.2,0.2-0.3,0.3-0.5c0-0.1,0.1-0.1,0.1-0.2l0.1,0c0,0,0,0,0,0l-0.1-0.1l0.1-0.1
|
||||
c0,0,0-0.1,0.1-0.1l0.1-0.1c0,0,0.1-0.1,0.1-0.1c0-0.1,0.1-0.1,0.1-0.2c0-0.1,0.1-0.1,0.1-0.2c0-0.1,0-0.1,0-0.2
|
||||
c0.1-0.3,0.1-0.7-0.1-1.1c0-0.1,0-0.1-0.1-0.2c0-0.1-0.1-0.2-0.1-0.2c0-0.1-0.1-0.2-0.1-0.2h-7.2l0,7.5c0,0,0,0,0,0.1
|
||||
c0,0.1-0.1,0.2-0.1,0.3c0,0,0,0.1-0.1,0.1c0,0-0.1,0.1-0.1,0.1c0,0,0,0-0.1,0c-0.1,0.1-0.3,0.2-0.4,0.3c-0.6,0.4-1.3,0.6-1.9,0.6
|
||||
c-0.1,0-0.2,0-0.3,0c-0.1,0-0.2,0-0.3,0c-0.4-0.1-0.7-0.2-1-0.4c-0.1-0.1-0.2-0.2-0.3-0.2c0,0-0.1-0.1-0.1-0.1h0l0,0l0,0
|
||||
c0,0-0.1-0.1-0.1-0.1L22,34.8c-0.2-0.2-0.4-0.3-0.6-0.4c-0.4-0.2-0.9-0.3-1.3-0.4c-0.3,0-0.7,0-1,0.1c-0.3,0.1-0.5,0.1-0.8,0.2
|
||||
c-0.5,0.2-1,0.5-1.3,0.9c-0.3,0.4-0.6,0.8-0.8,1.3C16,37,15.9,37.5,15.9,38c0,0.5,0.1,0.9,0.3,1.3c0.2,0.4,0.4,0.7,0.7,1
|
||||
c0.3,0.3,0.6,0.5,1,0.7c0.3,0.2,0.7,0.3,1,0.4c0.4,0.1,0.9,0.1,1.4,0c0.2,0,0.5-0.1,0.7-0.2c0.3-0.1,0.5-0.2,0.7-0.4
|
||||
c0.1-0.1,0.2-0.2,0.3-0.3l0.1-0.1l0,0l0,0c0,0,0,0,0,0l0.1-0.1c0.1,0,0.1-0.1,0.2-0.1c0.1-0.1,0.2-0.2,0.3-0.2
|
||||
c0.3-0.2,0.6-0.3,0.9-0.3c0.2,0,0.3,0,0.5,0h0.1c0.2,0,0.3,0,0.5,0c0.5,0.1,1,0.3,1.5,0.6c0.1,0.1,0.2,0.1,0.3,0.2l0.1,0
|
||||
c0,0,0.1,0.1,0.1,0.1c0,0.1,0.1,0.1,0.1,0.2c0,0.1,0,0.1,0.1,0.2c0,0,0,0,0,0.1l0,0v6.5C27.4,48.2,28,48.7,28.6,49.2z"/>
|
||||
<polygon points="22.3,34.9 22.3,34.9 22.3,34.9 22.3,34.9 "/>
|
||||
</g>
|
||||
<g>
|
||||
<polygon points="40.7,30.2 40.7,30.2 40.7,30.2 "/>
|
||||
<path fill="white" fill-opacity="0.5" d="M49.2,24c0.7-0.9,1.3-1.8,1.8-2.9c0.4-0.7,0.7-1.5,0.9-2.3c0.2-0.8,0.4-1.7,0.5-2.6c0.2-1.7,0.1-3.4-0.3-5
|
||||
c-0.3-1.3-0.8-2.6-1.5-3.8c-0.4-0.7-0.9-1.4-1.4-2.1c-0.6-0.7-1.3-1.4-2-2c-0.6-0.5-1.3-1-2-1.4c-0.8-0.4-1.5-0.8-2.3-1.1
|
||||
c-1-0.4-2-0.6-3.1-0.7C39.3,0.1,38.9,0,38.5,0c-0.2,0-0.4,0-0.6,0h-0.1c-0.5,0-0.9,0-1.4,0.1c-0.8,0.1-1.7,0.2-2.5,0.4
|
||||
c-0.9,0.2-1.7,0.6-2.5,0.9c-0.8,0.4-1.5,0.8-2.2,1.3c-0.6,0.4-1.1,0.9-1.6,1.4c-0.2,0.2-0.5,0.5-0.7,0.7V11
|
||||
c0.1,0.1,0.2,0.1,0.2,0.1c0.1,0,0.1,0.1,0.2,0.1c0.1,0,0.1,0.1,0.2,0.1c0.4,0.1,0.8,0.2,1.1,0.1c0.1,0,0.1,0,0.2,0
|
||||
c0.1,0,0.1-0.1,0.2-0.1c0.1,0,0.2-0.1,0.3-0.2c0,0,0.1-0.1,0.1-0.1l0,0c0,0,0.1,0,0.1-0.1l0.1-0.1c0.2-0.1,0.3-0.2,0.5-0.3
|
||||
c0.3-0.2,0.7-0.4,1-0.5c0.3-0.1,0.6-0.2,1-0.2c0.6-0.1,1.1-0.1,1.7,0c0.5,0.1,1,0.3,1.5,0.5c0.5,0.3,0.9,0.6,1.3,1
|
||||
c0.4,0.4,0.7,0.9,0.9,1.4c0.2,0.6,0.4,1.2,0.4,1.8c0,0.6-0.1,1.3-0.3,1.9c-0.2,0.7-0.6,1.3-1,1.8c-0.5,0.5-1.1,1-1.8,1.3
|
||||
c-0.4,0.1-0.7,0.3-1.1,0.3c-0.4,0.1-0.8,0.1-1.3,0.1c-0.4,0-0.7-0.1-1.1-0.2c-0.4-0.1-0.7-0.3-1-0.5c-0.2-0.1-0.3-0.2-0.5-0.3
|
||||
c-0.1,0-0.1-0.1-0.2-0.1l-0.1-0.1c0,0-0.1,0-0.1-0.1c0,0-0.1-0.1-0.1-0.1c0,0-0.1-0.1-0.1-0.1c-0.1-0.1-0.1-0.1-0.2-0.1
|
||||
c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1,0-0.2,0C28.4,18,28,18,27.6,18.1c-0.1,0-0.1,0-0.2,0.1c-0.1,0-0.2,0.1-0.2,0.1
|
||||
c-0.1,0-0.2,0.1-0.2,0.1v7.2l7.5,0c0,0,0,0,0.1,0c0.1,0,0.2,0.1,0.3,0.1c0,0,0.1,0,0.1,0.1c0,0,0.1,0.1,0.1,0.1l0.1,0.1
|
||||
c0.1,0.1,0.2,0.3,0.3,0.4c0.4,0.6,0.6,1.3,0.6,1.9c0,0.1,0,0.2,0,0.3c0,0.1,0,0.2,0,0.3c-0.1,0.4-0.2,0.7-0.4,1
|
||||
c-0.1,0.1-0.2,0.2-0.2,0.3c0,0-0.1,0.1-0.1,0.1l0,0l0,0l-0.2,0.2c-0.1,0.1-0.2,0.2-0.2,0.3c-0.1,0.2-0.3,0.5-0.3,0.7
|
||||
c-0.2,0.7-0.3,1.4-0.1,2c0.1,0.4,0.2,0.7,0.4,1.1c0.2,0.4,0.5,0.7,0.8,0.9c0.3,0.3,0.7,0.5,1.1,0.7c0.4,0.2,0.9,0.3,1.4,0.3
|
||||
c0,0,0,0,0,0c0.5,0,1-0.1,1.4-0.3c0.5-0.2,0.9-0.5,1.3-0.8c0.4-0.4,0.7-0.9,0.9-1.4c0.2-0.6,0.3-1.2,0.2-1.8
|
||||
c-0.1-0.5-0.2-0.9-0.5-1.3c-0.1-0.2-0.3-0.4-0.4-0.6l-0.1-0.1l0,0l0,0l0,0l0,0l0-0.1l0,0l-0.1,0.1l-0.1-0.1
|
||||
c-0.1-0.1-0.1-0.1-0.1-0.2c-0.1-0.1-0.2-0.3-0.2-0.4c-0.1-0.1-0.1-0.3-0.2-0.4c0-0.1-0.1-0.2-0.1-0.4c0-0.1,0-0.2,0-0.3
|
||||
c-0.1-0.6,0.1-1.3,0.4-1.9c0.1-0.2,0.2-0.4,0.4-0.6c0,0,0.1-0.1,0.1-0.1c0,0,0.1-0.1,0.1-0.1c0,0,0.1,0,0.1-0.1
|
||||
c0.1-0.1,0.2-0.1,0.3-0.1c0,0,0,0,0.1,0l0,0h6.5C48.2,25.1,48.7,24.5,49.2,24z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="XMLID_14_">
|
||||
</g>
|
||||
<g id="XMLID_15_">
|
||||
</g>
|
||||
<g id="XMLID_16_">
|
||||
</g>
|
||||
<g id="XMLID_17_">
|
||||
</g>
|
||||
<g id="XMLID_18_">
|
||||
</g>
|
||||
<g id="XMLID_19_">
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.4 KiB |
23
apps/ios/cleverrc/index.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, minimal-ui">
|
||||
<link rel="stylesheet" href="main.css">
|
||||
<script src="roslib.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="telemetry"><span class="mode">DISCONNECTED</span></div>
|
||||
<div class="battery"></div>
|
||||
<div class="logo"></div>
|
||||
<div class="container">
|
||||
<div class="stick stick-left">
|
||||
<div class="stick-pointer"></div>
|
||||
</div>
|
||||
<div class="stick stick-right">
|
||||
<div class="stick-pointer"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="main.js" type="text/javascript"></script>
|
||||
<script src="telemetry.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
91
apps/ios/cleverrc/main.css
Normal file
@@ -0,0 +1,91 @@
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
user-select: none;
|
||||
font-family: sans-serif;
|
||||
background: #212121;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.stick {
|
||||
border-radius: 50%;
|
||||
width: 5cm;
|
||||
height: 5cm;
|
||||
position: relative;
|
||||
transform: translateZ(0);
|
||||
border: 4px solid rgba(255,255,255,.4);
|
||||
box-shadow: 0 0 0 1px rgba(0,0,0,.2), inset 0 0 0 1px rgba(0,0,0,.2);
|
||||
}
|
||||
|
||||
.stick-pointer {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(255,255,255,.25);
|
||||
box-shadow: 0 0 10px rgba(0,0,0,.3);
|
||||
width: 3cm;
|
||||
height: 3cm;
|
||||
margin-left: -1.5cm;
|
||||
margin-top: -1.5cm;
|
||||
top: 2.5cm;
|
||||
left: 2.5cm;
|
||||
pointer-events: none;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.telemetry {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
top: 30px;
|
||||
font-size: 20px;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
body.armed .telemetry .mode {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@keyframes scale {
|
||||
0% { transform: scale(1.0); }
|
||||
50% { transform: scale(1.2); }
|
||||
100% { transform: scale(1.0); }
|
||||
}
|
||||
|
||||
.battery {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
bottom: 30px;
|
||||
font-size: 20px;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
body.low-battery .battery {
|
||||
color: #ff554b;
|
||||
animation: scale 0.3s 1 ease-in-out}
|
||||
|
||||
.logo {
|
||||
position: absolute;
|
||||
background: url(clever.svg);
|
||||
-webkit-background-size: 50px;
|
||||
background-size: 50px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -25px;
|
||||
margin-left: -25px;
|
||||
font-size: 20px;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
126
apps/ios/cleverrc/main.js
Normal file
@@ -0,0 +1,126 @@
|
||||
function throttle(func, ms) {
|
||||
var isThrottled = false,
|
||||
savedArgs,
|
||||
savedThis;
|
||||
|
||||
function wrapper() {
|
||||
if (isThrottled) {
|
||||
savedArgs = arguments;
|
||||
savedThis = this;
|
||||
return;
|
||||
}
|
||||
func.apply(this, arguments);
|
||||
isThrottled = true;
|
||||
setTimeout(function() {
|
||||
isThrottled = false;
|
||||
if (savedArgs) {
|
||||
wrapper.apply(savedThis, savedArgs);
|
||||
savedArgs = savedThis = null;
|
||||
}
|
||||
}, ms);
|
||||
}
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
function callNativeApp(name, msg) {
|
||||
try {
|
||||
webkit.messageHandlers[name].postMessage(msg);
|
||||
return true;
|
||||
} catch(err) {
|
||||
console.warn('The native context does not exist yet');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var rcLastPublish = null;
|
||||
|
||||
function rcPublish() {
|
||||
callNativeApp('control', controlMessage);
|
||||
rcLastPublish = new Date();
|
||||
}
|
||||
|
||||
rcPublishThrottled = throttle(rcPublish, 30);
|
||||
|
||||
setInterval(function() {
|
||||
if (rcLastPublish !== null && new Date() - rcLastPublish > 800) {
|
||||
rcPublishThrottled();
|
||||
}
|
||||
}, 50);
|
||||
|
||||
var body = document.querySelector('body');
|
||||
var stickLeft = document.querySelector('.stick-left');
|
||||
var stickRight = document.querySelector('.stick-right');
|
||||
|
||||
var controlMessage = { x: 0, y: 0, z: 0, r: 0 };
|
||||
|
||||
function onStickTouchMove(touch) {
|
||||
var target = touch.target;
|
||||
var targetRect = target.getBoundingClientRect();
|
||||
var stickPointer = target.querySelector('.stick-pointer');
|
||||
|
||||
var offsetX = touch.clientX - targetRect.left;
|
||||
var offsetY = touch.clientY - targetRect.top;
|
||||
|
||||
var x = 2 * offsetX / targetRect.width;
|
||||
var y = 2 * offsetY / targetRect.height;
|
||||
|
||||
x = Math.max(0, x);
|
||||
x = Math.min(2, x);
|
||||
y = Math.max(0, y);
|
||||
y = Math.min(2, y);
|
||||
|
||||
stickPointer.style.left = (x * 50) + '%';
|
||||
stickPointer.style.top = (y * 50) + '%';
|
||||
|
||||
x -= 1;
|
||||
y = 1 - y;
|
||||
|
||||
if (target.matches('.stick-left')) {
|
||||
controlMessage.z = Math.round((y + 1) * 500);
|
||||
controlMessage.r = Math.round(x * 1000);
|
||||
} else if (target.matches('.stick-right')) {
|
||||
controlMessage.x = Math.round(y * 1000);
|
||||
controlMessage.y = Math.round(x * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
body.addEventListener('touchmove', function (e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
function stickTouchStart(e) {
|
||||
setControlMode();
|
||||
callNativeApp('controlStart');
|
||||
onStickTouchMove(e.changedTouches[0]);
|
||||
rcPublishThrottled();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function stickTouchMove(e) {
|
||||
onStickTouchMove(e.changedTouches[0]);
|
||||
rcPublishThrottled();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function stickTouchEnd(e) {
|
||||
var pointer = e.target.querySelector('.stick-pointer');
|
||||
if (e.target.matches('.stick-left')) {
|
||||
controlMessage.r = 0;
|
||||
pointer.style.left = '50%';
|
||||
} else if (e.target.matches('.stick-right')) {
|
||||
controlMessage.x = 0;
|
||||
controlMessage.y = 0;
|
||||
pointer.style.left = '50%';
|
||||
pointer.style.top = '50%';
|
||||
}
|
||||
rcPublishThrottled();
|
||||
}
|
||||
|
||||
stickLeft.addEventListener('touchmove', stickTouchMove);
|
||||
stickRight.addEventListener('touchmove', stickTouchMove);
|
||||
stickLeft.addEventListener('touchstart', stickTouchStart);
|
||||
stickRight.addEventListener('touchstart', stickTouchStart);
|
||||
stickLeft.addEventListener('touchend', stickTouchEnd);
|
||||
stickRight.addEventListener('touchend', stickTouchEnd);
|
||||
3693
apps/ios/cleverrc/roslib.js
vendored
Normal file
89
apps/ios/cleverrc/telemetry.js
Normal file
@@ -0,0 +1,89 @@
|
||||
var url = 'ws://192.168.11.1:9090';
|
||||
var modeEl = document.querySelector('.telemetry .mode');
|
||||
var batteryEl = document.querySelector('.battery');
|
||||
|
||||
var ros = new ROSLIB.Ros({ url: url });
|
||||
|
||||
ros.on('connection', function () {
|
||||
body.classList.add('connected');
|
||||
});
|
||||
|
||||
ros.on('close', function () {
|
||||
body.classList.remove('connected');
|
||||
modeEl.classList.remove('armed');
|
||||
modeEl.innerHTML = 'DISCONNECTED';
|
||||
batteryEl.innerHTML = '';
|
||||
setTimeout(function() {
|
||||
modeEl.innerHTML = 'RECONNECTING';
|
||||
ros.connect(url);
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
var fcuState;
|
||||
|
||||
new ROSLIB.Topic({
|
||||
ros: ros,
|
||||
name: '/state_latched',
|
||||
messageType: 'mavros_msgs/State'
|
||||
}).subscribe(function(message) {
|
||||
body.classList.toggle('fcu-disconnected', !message.connected);
|
||||
body.classList.toggle('armed', message.armed);
|
||||
fcuState = message;
|
||||
modeEl.classList.toggle('armed', fcuState.armed);
|
||||
modeEl.innerHTML = message.connected ? fcuState.mode : 'DISCONNECTED FROM FCU';
|
||||
console.log('state', message);
|
||||
});
|
||||
|
||||
function notifyLowBattery() {
|
||||
callNativeApp('lowBattery');
|
||||
}
|
||||
|
||||
notifyLowBatteryThrottled = throttle(notifyLowBattery, 10000);
|
||||
|
||||
new ROSLIB.Topic({
|
||||
ros: ros,
|
||||
name: '/mavros/battery',
|
||||
messageType: 'sensor_msgs/BatteryState',
|
||||
throttle_rate: 5000
|
||||
}).subscribe(function(message) {
|
||||
var LOW_BATTERY = 3.8;
|
||||
batteryEl.innerHTML = (message.cell_voltage[0].toFixed(2) + ' V') || '';
|
||||
|
||||
if (message.cell_voltage[0] < LOW_BATTERY) {
|
||||
console.log('low battery');
|
||||
callNativeApp('lowBattery');
|
||||
body.classList.remove('low-battery');
|
||||
void body.offsetWidth; // trick for repeating animation
|
||||
body.classList.add('low-battery');
|
||||
} else {
|
||||
body.classList.remove('low-battery');
|
||||
}
|
||||
});
|
||||
|
||||
new ROSLIB.Topic({
|
||||
ros: ros,
|
||||
name: '/rosout_agg',
|
||||
messageType: 'rosgraph_msgs/Log'
|
||||
}).subscribe(function(message) {
|
||||
var BLACKLIST = ['CMD: ', 'PR: ', 'DROPPED'];
|
||||
if(message.level >= 4) {
|
||||
if (BLACKLIST.some(function(e) {
|
||||
return message.msg.indexOf(e) != -1;
|
||||
})) {
|
||||
console.log('Filtered out message ' + message.msg);
|
||||
return;
|
||||
}
|
||||
callNativeApp('notification', message);
|
||||
}
|
||||
});
|
||||
|
||||
var setMode = new ROSLIB.Service({
|
||||
ros: ros,
|
||||
name : '/mavros/set_mode',
|
||||
serviceType : 'mavros_msgs/SetMode'
|
||||
});
|
||||
|
||||
function setControlMode() {
|
||||
var CONTROL_MODE = 'STABILIZED';
|
||||
setMode.callService(new ROSLIB.ServiceRequest({ custom_mode: CONTROL_MODE }));
|
||||
}
|
||||
@@ -16,6 +16,11 @@
|
||||
#include <stdio.h>
|
||||
#include <tf/transform_broadcaster.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
using std::vector;
|
||||
using std::string;
|
||||
|
||||
namespace aruco_pose {
|
||||
|
||||
class ArucoPose : public nodelet::Nodelet {
|
||||
@@ -100,6 +105,45 @@ cv::Ptr<cv::aruco::Board> createCustomGridBoard(int markersX, int markersY, floa
|
||||
return res;
|
||||
}
|
||||
|
||||
cv::Ptr<cv::aruco::Board> createCustomBoard(std::map<string, string> markers, const cv::Ptr<cv::aruco::Dictionary> &dictionary) {
|
||||
cv::Ptr<cv::aruco::Board> res = cv::makePtr<cv::aruco::Board>();
|
||||
|
||||
res->dictionary = dictionary;
|
||||
|
||||
size_t total_markers = markers.size();
|
||||
res->ids.reserve(total_markers);
|
||||
res->objPoints.reserve(total_markers);
|
||||
|
||||
// Generate ids and objPoints
|
||||
for(auto const &marker : markers) {
|
||||
res->ids.push_back(std::stoi(marker.first));
|
||||
|
||||
vector<string> parts;
|
||||
parts = strSplit(marker.second, " ");
|
||||
|
||||
float size = std::stof(parts.at(0));
|
||||
float x = std::stof(parts.at(1));
|
||||
float y = std::stof(parts.at(2));
|
||||
float z = std::stof(parts.at(3));
|
||||
float yaw = std::stof(parts.at(4));
|
||||
float pitch = std::stof(parts.at(5));
|
||||
float roll = std::stof(parts.at(6));
|
||||
|
||||
vector<cv::Point3f> corners;
|
||||
corners.resize(4);
|
||||
corners[0] = cv::Point3f(x - size / 2, y + size / 2, 0);
|
||||
corners[1] = corners[0] + cv::Point3f(size, 0, 0);
|
||||
corners[2] = corners[0] + cv::Point3f(size, -size, 0);
|
||||
corners[3] = corners[0] + cv::Point3f(0, -size, 0);
|
||||
|
||||
// TODO: process yaw, pitch, roll
|
||||
|
||||
res->objPoints.push_back(corners);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#include "fix.cpp"
|
||||
|
||||
void ArucoPose::createBoard()
|
||||
@@ -178,8 +222,22 @@ void ArucoPose::createBoard()
|
||||
}
|
||||
else if (type == "custom")
|
||||
{
|
||||
// Not implemented yet
|
||||
ROS_FATAL("Custom boards are not implemented yet.");
|
||||
ROS_INFO("Initialize a custom board");
|
||||
|
||||
std::map<string, string> markers;
|
||||
nh_priv_.getParam("markers", markers);
|
||||
|
||||
board = createCustomBoard(markers, dictionary);
|
||||
|
||||
ROS_INFO("Draw a custom board");
|
||||
// Publish map image for debugging
|
||||
_drawPlanarBoard(board, cv::Size(2000, 2000), map_image, 50, 1);
|
||||
|
||||
cv::cvtColor(map_image, map_image, CV_GRAY2BGR);
|
||||
|
||||
map_image_msg.encoding = sensor_msgs::image_encodings::BGR8;
|
||||
map_image_msg.image = map_image;
|
||||
map_image_pub.publish(map_image_msg.toImageMsg());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -214,10 +272,12 @@ void ArucoPose::detect(const sensor_msgs::ImageConstPtr& msg, const sensor_msgs:
|
||||
cv::Mat distCoeffs(8, 1, CV_64F);
|
||||
parseCameraInfo(cinfo, cameraMatrix, distCoeffs);
|
||||
|
||||
int valid = 0;
|
||||
cv::Mat rvec, tvec, objPoints;
|
||||
|
||||
if (markerIds.size() > 0) {
|
||||
|
||||
cv::Mat rvec, tvec, objPoints;
|
||||
int valid = _estimatePoseBoard(markerCorners, markerIds, board, cameraMatrix, distCoeffs,
|
||||
valid = _estimatePoseBoard(markerCorners, markerIds, board, cameraMatrix, distCoeffs,
|
||||
rvec, tvec, false, objPoints);
|
||||
|
||||
if (valid) {
|
||||
@@ -243,18 +303,16 @@ void ArucoPose::detect(const sensor_msgs::ImageConstPtr& msg, const sensor_msgs:
|
||||
ref_transform.setOrigin(ref_vector3);
|
||||
ref_transform.setRotation(q);
|
||||
br.sendTransform(ref_transform);
|
||||
|
||||
if(img_pub.getNumSubscribers() > 0)
|
||||
{
|
||||
// Publish debug image
|
||||
cv::aruco::drawDetectedMarkers(image, markerCorners, markerIds);
|
||||
cv::aruco::drawAxis(image, cameraMatrix, distCoeffs, rvec, tvec, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (img_pub.getNumSubscribers() > 0)
|
||||
{
|
||||
cv::aruco::drawDetectedMarkers(image, markerCorners, markerIds); // draw markers
|
||||
if (valid)
|
||||
{
|
||||
cv::aruco::drawAxis(image, cameraMatrix, distCoeffs, rvec, tvec, 0.3); // draw board axis
|
||||
}
|
||||
cv_bridge::CvImage out_msg;
|
||||
out_msg.header.frame_id = msg->header.frame_id;
|
||||
out_msg.header.stamp = msg->header.stamp;
|
||||
|
||||
20
aruco_pose/src/util.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
std::vector<std::string> strSplit(const std::string& str, const std::string& delim)
|
||||
{
|
||||
std::vector<std::string> tokens;
|
||||
size_t prev = 0, pos = 0;
|
||||
do
|
||||
{
|
||||
pos = str.find(delim, prev);
|
||||
if (pos == std::string::npos) pos = str.length();
|
||||
std::string token = str.substr(prev, pos-prev);
|
||||
if (!token.empty()) tokens.push_back(token);
|
||||
prev = pos + delim.length();
|
||||
}
|
||||
while (pos < str.length() && prev < str.length());
|
||||
return tokens;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.8 MiB |
|
Before Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 6.5 MiB |
|
Before Width: | Height: | Size: 2.4 MiB |
|
Before Width: | Height: | Size: 363 KiB |
|
Before Width: | Height: | Size: 257 KiB |
|
Before Width: | Height: | Size: 467 KiB |
|
Before Width: | Height: | Size: 404 KiB |
|
Before Width: | Height: | Size: 265 KiB |
|
Before Width: | Height: | Size: 475 KiB |
|
Before Width: | Height: | Size: 3.1 MiB |
|
Before Width: | Height: | Size: 518 KiB |
|
Before Width: | Height: | Size: 210 KiB |
|
Before Width: | Height: | Size: 332 KiB |
|
Before Width: | Height: | Size: 326 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 237 KiB |
|
Before Width: | Height: | Size: 414 KiB |
|
Before Width: | Height: | Size: 552 KiB |
|
Before Width: | Height: | Size: 246 KiB |
|
Before Width: | Height: | Size: 425 KiB |
|
Before Width: | Height: | Size: 279 KiB |
|
Before Width: | Height: | Size: 203 KiB |
|
Before Width: | Height: | Size: 327 KiB |
|
Before Width: | Height: | Size: 251 KiB |
|
Before Width: | Height: | Size: 408 KiB |
|
Before Width: | Height: | Size: 339 KiB |
|
Before Width: | Height: | Size: 294 KiB |
|
Before Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 329 KiB |
|
Before Width: | Height: | Size: 848 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 335 KiB |
|
Before Width: | Height: | Size: 772 KiB |
|
Before Width: | Height: | Size: 334 KiB |
|
Before Width: | Height: | Size: 305 KiB |
|
Before Width: | Height: | Size: 321 KiB |
13
book.json
@@ -3,5 +3,14 @@
|
||||
"description": "Конструктор квадрокоптера «Клевер»",
|
||||
"author": "Copter Express",
|
||||
"language": "ru",
|
||||
"plugins": ["youtube", "richquotes"]
|
||||
}
|
||||
"root": "docs/",
|
||||
"plugins": ["youtube", "richquotes", "disqus", "versions"],
|
||||
"pluginsConfig": {
|
||||
"disqus": {
|
||||
"shortName": "coex-clever"
|
||||
},
|
||||
"versions": {
|
||||
"type": "tags"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ find_package(catkin REQUIRED COMPONENTS
|
||||
nodelet
|
||||
pluginlib
|
||||
roscpp
|
||||
genmsg
|
||||
rospy
|
||||
std_msgs
|
||||
message_generation
|
||||
@@ -68,15 +69,11 @@ add_service_files(
|
||||
FILES
|
||||
GetTelemetry.srv
|
||||
Navigate.srv
|
||||
NavigateGlobal.srv
|
||||
SetPosition.srv
|
||||
SetPositionYawRate.srv
|
||||
SetPositionGlobal.srv
|
||||
SetPositionGlobalYawRate.srv
|
||||
SetVelocity.srv
|
||||
SetVelocityYawRate.srv
|
||||
SetAttitude.srv
|
||||
SetAttitudeYawRate.srv
|
||||
SetRatesYaw.srv
|
||||
SetRates.srv
|
||||
)
|
||||
|
||||
@@ -157,7 +154,9 @@ add_library(aruco_vpe
|
||||
## Declare a C++ executable
|
||||
## With catkin_make all packages are built within a single CMake context
|
||||
## The recommended prefix ensures that target names across packages don't collide
|
||||
# add_executable(${PROJECT_NAME}_node src/clever_node.cpp)
|
||||
add_executable(rc src/rc.cpp)
|
||||
|
||||
target_link_libraries(rc ${catkin_LIBRARIES})
|
||||
|
||||
## Rename C++ executable without prefix
|
||||
## The above recommended prefix causes long target names, the following renames the
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<arg name="rosbridge" default="true"/>
|
||||
<arg name="main_camera" default="true"/>
|
||||
<arg name="aruco" default="false"/>
|
||||
<arg name="rc" value="true"/>
|
||||
<arg name="fpv_camera" default="false"/>
|
||||
<arg name="fpv_camera_device" default="/dev/v4l/by-id/usb-HD_Camera_Manufacturer_USB_2.0_Camera-video-index0"/>
|
||||
<arg name="arduino" default="false"/>
|
||||
@@ -46,7 +47,10 @@
|
||||
<include file="$(find clever)/launch/main_camera.launch" if="$(arg main_camera)"/>
|
||||
|
||||
<!-- rosbridge -->
|
||||
<include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch" if="$(arg rosbridge)"/>
|
||||
<include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch" if="$(eval rosbridge or rc)"/>
|
||||
|
||||
<!-- rc backend -->
|
||||
<node name="rc" pkg="clever" type="rc" output="screen" if="$(arg rc)"/>
|
||||
|
||||
<!-- FPV video streaming -->
|
||||
<include file="$(find clever)/launch/fpv_camera.launch" if="$(arg fpv_camera)">
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
<arg name="fcu_ip" default="127.0.0.1"/>
|
||||
<arg name="gcs_bridge" default="tcp"/>
|
||||
<arg name="viz" default="true"/>
|
||||
<arg name="respawn" default="false"/>
|
||||
|
||||
<node pkg="mavros" type="mavros_node" name="mavros" required="false" clear_params="true" output="screen">
|
||||
<node pkg="mavros" type="mavros_node" name="mavros" required="false" clear_params="true" respawn="$(arg respawn)" respawn_delay="3" output="screen">
|
||||
<!-- UART connection -->
|
||||
<param name="fcu_url" value="/dev/ttyAMA0:921600" if="$(eval fcu_conn is None or fcu_conn == 'uart')"/>
|
||||
|
||||
@@ -17,6 +18,7 @@
|
||||
<!-- gcs bridge -->
|
||||
<param name="gcs_url" value="tcp-l://0.0.0.0:5760" if="$(eval gcs_bridge == 'tcp')"/>
|
||||
<param name="gcs_url" value="udp://@192.168.11.14:14550" if="$(eval gcs_bridge == 'udp')"/> <!-- TODO: fix -->
|
||||
<param name="gcs_url" value="udp-b://192.168.11.1:14550@" if="$(eval gcs_bridge == 'udp-b')"/>
|
||||
<param name="gcs_url" value="" if="$(eval not gcs_bridge)"/>
|
||||
|
||||
<!-- default px4 params -->
|
||||
@@ -28,6 +30,7 @@
|
||||
<param name="local_position/tf/frame_id" value="local_origin"/>
|
||||
<param name="local_position/tf/child_frame_id" value="fcu"/>
|
||||
<param name="global_position/tf/send" value="false"/>
|
||||
<param name="imu/frame_id" value="fcu"/>
|
||||
<rosparam param="plugin_blacklist">
|
||||
- safety_area
|
||||
- image_pub
|
||||
@@ -37,7 +40,6 @@
|
||||
- 3dr_radio
|
||||
- actuator_control
|
||||
- hil_controls
|
||||
- manual_control
|
||||
- vfr_hud
|
||||
- px4flow
|
||||
- vision_speed_estimate
|
||||
|
||||
@@ -20,14 +20,15 @@ using std::string;
|
||||
class ArucoVPE : public nodelet::Nodelet
|
||||
{
|
||||
public:
|
||||
ArucoVPE() :
|
||||
ArucoVPE() :
|
||||
last_published_(0),
|
||||
lookup_timeout_(0.05)
|
||||
{}
|
||||
|
||||
private:
|
||||
private:
|
||||
ros::Time last_published_;
|
||||
ros::Duration lookup_timeout_;
|
||||
ros::Duration reset_timeout_;
|
||||
ros::Publisher vision_position_pub_;
|
||||
ros::Timer dummy_vision_timer_;
|
||||
string aruco_orientation_;
|
||||
@@ -42,6 +43,9 @@ private:
|
||||
bool use_mocap;
|
||||
nh_priv.param<bool>("use_mocap", use_mocap, false);
|
||||
nh_priv.param<bool>("reset_vpe", reset_vpe_, !use_mocap);
|
||||
double reset_timeout;
|
||||
nh_priv.param("reset_timeout", reset_timeout, 2.0);
|
||||
reset_timeout_ = ros::Duration(reset_timeout);
|
||||
|
||||
static ros::Subscriber pose_sub = nh.subscribe("mavros/local_position/pose", 1, &ArucoVPE::handlePose, this);
|
||||
static ros::Subscriber aruco_pose_sub = nh.subscribe("aruco_pose/pose", 1, &ArucoVPE::handleArucoPose, this);
|
||||
@@ -100,7 +104,7 @@ private:
|
||||
br.sendTransform(t);
|
||||
|
||||
if (last_published_.toSec() == 0 || // no vpe has been posted
|
||||
(reset_vpe_ && (ros::Time::now() - last_published_ > ros::Duration(2)))) // vpe origin outdated
|
||||
(reset_vpe_ && (ros::Time::now() - last_published_ > reset_timeout_))) // vpe origin outdated
|
||||
{
|
||||
ROS_INFO("Reset VPE");
|
||||
t = tf_buffer.lookupTransform("local_origin", "aruco_map_vision", stamp, lookup_timeout_);
|
||||
|
||||
@@ -9,8 +9,15 @@ from sensor_msgs.msg import NavSatFix
|
||||
def global_to_local(lat, lon):
|
||||
# TODO: refactor
|
||||
|
||||
position_global = rospy.wait_for_message('mavros/global_position/global', NavSatFix, timeout=5)
|
||||
pose = rospy.wait_for_message('mavros/local_position/pose', PoseStamped, timeout=5)
|
||||
try:
|
||||
position_global = rospy.wait_for_message('mavros/global_position/global', NavSatFix, timeout=0.5)
|
||||
except rospy.exceptions.ROSException:
|
||||
raise Exception('No global position')
|
||||
|
||||
try:
|
||||
pose = rospy.wait_for_message('mavros/local_position/pose', PoseStamped, timeout=0.5)
|
||||
except rospy.exceptions.ROSException:
|
||||
raise Exception('No local position')
|
||||
|
||||
d = math.hypot(pose.pose.position.x, pose.pose.position.y)
|
||||
|
||||
|
||||
135
clever/src/rc.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
// CLEVER mobile remote control support:
|
||||
// * Send ManualControl messages through UDP
|
||||
// * `latched_state` topic
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <errno.h>
|
||||
#include <thread>
|
||||
#include "ros/ros.h"
|
||||
#include "std_msgs/String.h"
|
||||
#include "mavros_msgs/State.h"
|
||||
#include "mavros_msgs/ManualControl.h"
|
||||
|
||||
struct ControlMessage
|
||||
{
|
||||
int16_t x, y, z, r;
|
||||
} __attribute__((packed));
|
||||
|
||||
class RC
|
||||
{
|
||||
public:
|
||||
RC():
|
||||
nh(),
|
||||
nh_priv("~")
|
||||
{
|
||||
// Create socket thread
|
||||
std::thread t(&RC::socketThread, this);
|
||||
t.detach();
|
||||
|
||||
initLatchedState();
|
||||
}
|
||||
|
||||
private:
|
||||
ros::NodeHandle nh, nh_priv;
|
||||
ros::Subscriber state_sub;
|
||||
ros::Publisher state_pub;
|
||||
ros::Timer state_timeout_timer;
|
||||
mavros_msgs::StateConstPtr state_msg;
|
||||
|
||||
void handleState(const mavros_msgs::StateConstPtr& state)
|
||||
{
|
||||
state_timeout_timer.setPeriod(ros::Duration(3), true);
|
||||
state_timeout_timer.start();
|
||||
|
||||
if (!state_msg ||
|
||||
state->connected != state_msg->connected ||
|
||||
state->mode != state_msg->mode ||
|
||||
state->armed != state_msg->armed) {
|
||||
state_msg = state;
|
||||
state_pub.publish(state_msg);
|
||||
}
|
||||
}
|
||||
|
||||
void stateTimedOut(const ros::TimerEvent&)
|
||||
{
|
||||
ROS_INFO("State timeout");
|
||||
mavros_msgs::State unknown_state;
|
||||
state_pub.publish(unknown_state);
|
||||
state_msg = nullptr;
|
||||
}
|
||||
|
||||
void initLatchedState()
|
||||
{
|
||||
state_sub = nh.subscribe("mavros/state", 1, &RC::handleState, this);
|
||||
state_pub = nh.advertise<mavros_msgs::State>("state_latched", 1, true);
|
||||
state_timeout_timer = nh.createTimer(ros::Duration(0), &RC::stateTimedOut, this, true, false);
|
||||
|
||||
// Publish initial state
|
||||
mavros_msgs::State unknown_state;
|
||||
state_pub.publish(unknown_state);
|
||||
}
|
||||
|
||||
int createSocket(int port)
|
||||
{
|
||||
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
|
||||
sockaddr_in sin;
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
sin.sin_port = htons(port);
|
||||
|
||||
if (bind(sockfd, (sockaddr *)&sin, sizeof(sin)) < 0) {
|
||||
ROS_FATAL("socket bind error: %s", strerror(errno));
|
||||
close(sockfd);
|
||||
ros::shutdown();
|
||||
}
|
||||
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
void socketThread()
|
||||
{
|
||||
int port;
|
||||
nh_priv.param("port", port, 35602);
|
||||
int sockfd = createSocket(port);
|
||||
|
||||
char buff[9999];
|
||||
|
||||
ros::Publisher manual_control_pub = nh.advertise<mavros_msgs::ManualControl>("mavros/manual_control/send", 1);
|
||||
mavros_msgs::ManualControl manual_control_msg;
|
||||
|
||||
sockaddr_in client_addr;
|
||||
socklen_t client_addr_size = sizeof(client_addr);
|
||||
|
||||
ROS_INFO("UDP RC initialized on port %d", port);
|
||||
|
||||
while (true) {
|
||||
// read next UDP packet
|
||||
int bsize = recvfrom(sockfd, &buff[0], sizeof(buff) - 1, 0, (sockaddr *) &client_addr, &client_addr_size);
|
||||
|
||||
if (bsize < 0) {
|
||||
ROS_ERROR("recvfrom() error: %s", strerror(errno));
|
||||
} else if (bsize != sizeof(ControlMessage)) {
|
||||
ROS_ERROR_THROTTLE(30, "Wrong UDP packet size: %d", bsize);
|
||||
}
|
||||
|
||||
// unpack message
|
||||
// warning: ignore endianness, so the code is platform-dependent
|
||||
ControlMessage *msg = (ControlMessage *)buff;
|
||||
|
||||
manual_control_msg.x = msg->x;
|
||||
manual_control_msg.y = msg->y;
|
||||
manual_control_msg.z = msg->z;
|
||||
manual_control_msg.r = msg->r;
|
||||
manual_control_pub.publish(manual_control_msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
ros::init(argc, argv, "rc");
|
||||
RC rc;
|
||||
ros::spin();
|
||||
}
|
||||
@@ -2,7 +2,8 @@
|
||||
from __future__ import division
|
||||
|
||||
import rospy
|
||||
from geometry_msgs.msg import TransformStamped, PoseStamped, Point, PointStamped, Vector3, Vector3Stamped, TwistStamped, QuaternionStamped
|
||||
from geometry_msgs.msg import TransformStamped, PoseStamped, Point, PointStamped, Vector3, \
|
||||
Vector3Stamped, TwistStamped, QuaternionStamped
|
||||
from sensor_msgs.msg import NavSatFix, BatteryState
|
||||
import tf2_ros
|
||||
import tf2_geometry_msgs
|
||||
@@ -75,12 +76,19 @@ rospy.Subscriber('/mavros/global_position/global', NavSatFix, global_position_up
|
||||
rospy.Subscriber('/mavros/battery', BatteryState, battery_update)
|
||||
|
||||
|
||||
PT = PositionTarget
|
||||
AT = AttitudeTarget
|
||||
AUTO_OFFBOARD = rospy.get_param('~auto_offboard', True)
|
||||
AUTO_ARM = AUTO_OFFBOARD and rospy.get_param('~auto_arm', True)
|
||||
OFFBOARD_TIMEOUT = rospy.Duration(rospy.get_param('~offboard_timeout', 3))
|
||||
ARM_TIMEOUT = rospy.Duration(rospy.get_param('~arm_timeout', 5))
|
||||
LOCAL_POSITION_TIMEOUT = rospy.Duration(rospy.get_param('~local_position_timeout', 0.5))
|
||||
NAVIGATE_AFTER_ARMED = rospy.Duration(rospy.get_param('~navigate_after_armed', False))
|
||||
TRANSFORM_TIMEOUT = rospy.Duration(rospy.get_param('~transform_timeout', 3))
|
||||
SETPOINT_RATE = rospy.get_param('~setpoint_rate', 30)
|
||||
LOCAL_FRAME = rospy.get_param('~local_frame', 'local_origin')
|
||||
LAND_MODE = rospy.get_param('~land_mode', 'AUTO.LAND')
|
||||
LAND_TIMEOUT = rospy.Duration(rospy.get_param('~land_timeout', 2))
|
||||
|
||||
|
||||
def offboard_and_arm():
|
||||
@@ -95,6 +103,7 @@ def offboard_and_arm():
|
||||
break
|
||||
if rospy.get_rostime() - start > OFFBOARD_TIMEOUT:
|
||||
raise Exception('OFFBOARD request timed out')
|
||||
rospy.sleep(0.1)
|
||||
|
||||
if AUTO_ARM and not state.armed:
|
||||
rospy.loginfo('Arming')
|
||||
@@ -104,9 +113,9 @@ def offboard_and_arm():
|
||||
while True:
|
||||
if state.armed:
|
||||
return True
|
||||
|
||||
if rospy.get_rostime() - start > ARM_TIMEOUT:
|
||||
raise Exception('Arming timed out')
|
||||
rospy.sleep(0.1)
|
||||
|
||||
|
||||
ps = PoseStamped()
|
||||
@@ -149,143 +158,84 @@ def get_publisher_and_message(req, stamp, continued=True, update_frame=True):
|
||||
ps.header.stamp = stamp
|
||||
vs.header.stamp = stamp
|
||||
|
||||
if isinstance(req, srv.NavigateRequest):
|
||||
if isinstance(req, (srv.NavigateRequest, srv.NavigateGlobalRequest)):
|
||||
global current_nav_start, current_nav_start_stamp, current_nav_finish
|
||||
|
||||
if update_frame:
|
||||
ps.header.frame_id = req.frame_id or 'local_origin'
|
||||
ps.pose.position = Point(req.x, req.y, req.z)
|
||||
ps.header.frame_id = req.frame_id or LOCAL_FRAME
|
||||
ps.pose.position = Point(getattr(req, 'x', 0), getattr(req, 'y', 0), req.z)
|
||||
ps.pose.orientation = orientation_from_euler(0, 0, req.yaw)
|
||||
current_nav_finish = tf_buffer.transform(ps, 'local_origin', TRANSFORM_TIMEOUT)
|
||||
current_nav_finish = tf_buffer.transform(ps, LOCAL_FRAME, TRANSFORM_TIMEOUT)
|
||||
|
||||
if isinstance(req, srv.NavigateGlobalRequest):
|
||||
# Recalculate x and y from lat and lon
|
||||
current_nav_finish.pose.position.x, current_nav_finish.pose.position.y = \
|
||||
global_to_local(req.lat, req.lon)
|
||||
|
||||
if not continued:
|
||||
current_nav_start = pose.pose.position
|
||||
current_nav_start_stamp = stamp
|
||||
|
||||
if NAVIGATE_AFTER_ARMED and not state.armed:
|
||||
current_nav_start_stamp = stamp
|
||||
|
||||
setpoint = get_navigate_setpoint(stamp, current_nav_start, current_nav_finish.pose.position,
|
||||
current_nav_start_stamp, req.speed)
|
||||
|
||||
msg = PositionTarget(coordinate_frame=PositionTarget.FRAME_LOCAL_NED,
|
||||
type_mask=PositionTarget.IGNORE_VX + PositionTarget.IGNORE_VY + PositionTarget.IGNORE_VZ +
|
||||
PositionTarget.IGNORE_AFX + PositionTarget.IGNORE_AFY + PositionTarget.IGNORE_AFZ +
|
||||
PositionTarget.IGNORE_YAW_RATE,
|
||||
yaw_rate_flag = math.isnan(req.yaw)
|
||||
msg = PositionTarget(coordinate_frame=PT.FRAME_LOCAL_NED,
|
||||
type_mask=PT.IGNORE_VX + PT.IGNORE_VY + PT.IGNORE_VZ +
|
||||
PT.IGNORE_AFX + PT.IGNORE_AFY + PT.IGNORE_AFZ +
|
||||
(PT.IGNORE_YAW if yaw_rate_flag else PT.IGNORE_YAW_RATE),
|
||||
position=setpoint,
|
||||
yaw=euler_from_orientation(current_nav_finish.pose.orientation)[2] - math.pi / 2)
|
||||
return position_pub, msg
|
||||
|
||||
elif isinstance(req, srv.SetPositionRequest):
|
||||
ps.header.frame_id = req.frame_id or 'local_origin'
|
||||
ps.pose.position = Point(req.x, req.y, req.z)
|
||||
ps.pose.orientation = orientation_from_euler(0, 0, req.yaw)
|
||||
pose_local = tf_buffer.transform(ps, 'local_origin', TRANSFORM_TIMEOUT)
|
||||
|
||||
msg = PositionTarget(coordinate_frame=PositionTarget.FRAME_LOCAL_NED,
|
||||
type_mask=PositionTarget.IGNORE_VX + PositionTarget.IGNORE_VY + PositionTarget.IGNORE_VZ +
|
||||
PositionTarget.IGNORE_AFX + PositionTarget.IGNORE_AFY + PositionTarget.IGNORE_AFZ +
|
||||
PositionTarget.IGNORE_YAW_RATE,
|
||||
position=pose_local.pose.position,
|
||||
yaw=euler_from_orientation(pose_local.pose.orientation)[2] - math.pi / 2)
|
||||
return position_pub, msg
|
||||
|
||||
elif isinstance(req, srv.SetPositionYawRateRequest):
|
||||
ps.header.frame_id = req.frame_id or 'local_origin'
|
||||
ps.pose.position = Point(req.x, req.y, req.z)
|
||||
pose_local = tf_buffer.transform(ps, 'local_origin', TRANSFORM_TIMEOUT)
|
||||
msg = PositionTarget(coordinate_frame=PositionTarget.FRAME_LOCAL_NED,
|
||||
type_mask=PositionTarget.IGNORE_VX + PositionTarget.IGNORE_VY + PositionTarget.IGNORE_VZ +
|
||||
PositionTarget.IGNORE_AFX + PositionTarget.IGNORE_AFY + PositionTarget.IGNORE_AFZ +
|
||||
PositionTarget.IGNORE_YAW,
|
||||
position=pose_local.pose.position,
|
||||
yaw=euler_from_orientation(current_nav_finish.pose.orientation)[2] - math.pi / 2,
|
||||
yaw_rate=req.yaw_rate)
|
||||
return position_pub, msg
|
||||
|
||||
elif isinstance(req, srv.SetPositionGlobalRequest):
|
||||
x, y = global_to_local(req.lat, req.lon)
|
||||
|
||||
ps.header.frame_id = req.frame_id or 'local_origin'
|
||||
ps.pose.position = Point(0, 0, req.z)
|
||||
elif isinstance(req, (srv.SetPositionRequest, srv.SetPositionGlobalRequest)):
|
||||
ps.header.frame_id = req.frame_id or LOCAL_FRAME
|
||||
ps.pose.position = Point(getattr(req, 'x', 0), getattr(req, 'y', 0), req.z)
|
||||
ps.pose.orientation = orientation_from_euler(0, 0, req.yaw)
|
||||
pose_local = tf_buffer.transform(ps, 'local_origin', TRANSFORM_TIMEOUT)
|
||||
pose_local.pose.position.x = x
|
||||
pose_local.pose.position.y = y
|
||||
pose_local = tf_buffer.transform(ps, LOCAL_FRAME, TRANSFORM_TIMEOUT)
|
||||
|
||||
msg = PositionTarget(coordinate_frame=PositionTarget.FRAME_LOCAL_NED,
|
||||
type_mask=PositionTarget.IGNORE_VX + PositionTarget.IGNORE_VY + PositionTarget.IGNORE_VZ +
|
||||
PositionTarget.IGNORE_AFX + PositionTarget.IGNORE_AFY + PositionTarget.IGNORE_AFZ +
|
||||
PositionTarget.IGNORE_YAW_RATE,
|
||||
position=pose_local.pose.position,
|
||||
yaw=euler_from_orientation(pose_local.pose.orientation)[2] - math.pi / 2)
|
||||
return position_pub, msg
|
||||
|
||||
elif isinstance(req, srv.SetPositionGlobalYawRateRequest):
|
||||
x, y = global_to_local(req.lat, req.lon)
|
||||
|
||||
ps.header.frame_id = req.frame_id or 'local_origin'
|
||||
ps.pose.position = Point(0, 0, req.z)
|
||||
pose_local = tf_buffer.transform(ps, 'local_origin', TRANSFORM_TIMEOUT)
|
||||
pose_local.pose.position.x = x
|
||||
pose_local.pose.position.y = y
|
||||
|
||||
msg = PositionTarget(coordinate_frame=PositionTarget.FRAME_LOCAL_NED,
|
||||
type_mask=PositionTarget.IGNORE_VX + PositionTarget.IGNORE_VY + PositionTarget.IGNORE_VZ +
|
||||
PositionTarget.IGNORE_AFX + PositionTarget.IGNORE_AFY + PositionTarget.IGNORE_AFZ +
|
||||
PositionTarget.IGNORE_YAW,
|
||||
if isinstance(req, srv.SetPositionGlobalRequest):
|
||||
pose_local.pose.position.x, pose_local.pose.position.y = global_to_local(req.lat, req.lon)
|
||||
|
||||
yaw_rate_flag = math.isnan(req.yaw)
|
||||
msg = PositionTarget(coordinate_frame=PT.FRAME_LOCAL_NED,
|
||||
type_mask=PT.IGNORE_VX + PT.IGNORE_VY + PT.IGNORE_VZ +
|
||||
PT.IGNORE_AFX + PT.IGNORE_AFY + PT.IGNORE_AFZ +
|
||||
(PT.IGNORE_YAW if yaw_rate_flag else PT.IGNORE_YAW_RATE),
|
||||
position=pose_local.pose.position,
|
||||
yaw=euler_from_orientation(pose_local.pose.orientation)[2] - math.pi / 2,
|
||||
yaw_rate=req.yaw_rate)
|
||||
return position_pub, msg
|
||||
|
||||
elif isinstance(req, srv.SetVelocityRequest):
|
||||
vs.vector = Vector3(req.vx, req.vy, req.vz)
|
||||
vs.header.frame_id = req.frame_id or 'local_origin'
|
||||
ps.header.frame_id = req.frame_id or 'local_origin'
|
||||
vs.header.frame_id = req.frame_id or LOCAL_FRAME
|
||||
ps.header.frame_id = req.frame_id or LOCAL_FRAME
|
||||
ps.pose.orientation = orientation_from_euler(0, 0, req.yaw)
|
||||
pose_local = tf_buffer.transform(ps, 'local_origin', TRANSFORM_TIMEOUT)
|
||||
vector_local = tf_buffer.transform(vs, 'local_origin', TRANSFORM_TIMEOUT)
|
||||
msg = PositionTarget(coordinate_frame=PositionTarget.FRAME_LOCAL_NED,
|
||||
type_mask=PositionTarget.IGNORE_PX + PositionTarget.IGNORE_PY + PositionTarget.IGNORE_PZ +
|
||||
PositionTarget.IGNORE_AFX + PositionTarget.IGNORE_AFY + PositionTarget.IGNORE_AFZ +
|
||||
PositionTarget.IGNORE_YAW_RATE,
|
||||
velocity=vector_local.vector,
|
||||
yaw=euler_from_orientation(pose_local.pose.orientation)[2] - math.pi / 2)
|
||||
return position_pub, msg
|
||||
pose_local = tf_buffer.transform(ps, LOCAL_FRAME, TRANSFORM_TIMEOUT)
|
||||
vector_local = tf_buffer.transform(vs, LOCAL_FRAME, TRANSFORM_TIMEOUT)
|
||||
|
||||
elif isinstance(req, srv.SetVelocityYawRateRequest):
|
||||
vs.vector = Vector3(req.vx, req.vy, req.vz)
|
||||
vs.header.frame_id = req.frame_id or 'local_origin'
|
||||
vector_local = tf_buffer.transform(vs, 'local_origin', TRANSFORM_TIMEOUT)
|
||||
msg = PositionTarget(coordinate_frame=PositionTarget.FRAME_LOCAL_NED,
|
||||
type_mask=PositionTarget.IGNORE_PX + PositionTarget.IGNORE_PY + PositionTarget.IGNORE_PZ +
|
||||
PositionTarget.IGNORE_AFX + PositionTarget.IGNORE_AFY + PositionTarget.IGNORE_AFZ +
|
||||
PositionTarget.IGNORE_YAW,
|
||||
yaw_rate_flag = math.isnan(req.yaw)
|
||||
msg = PositionTarget(coordinate_frame=PT.FRAME_LOCAL_NED,
|
||||
type_mask=PT.IGNORE_PX + PT.IGNORE_PY + PT.IGNORE_PZ +
|
||||
PT.IGNORE_AFX + PT.IGNORE_AFY + PT.IGNORE_AFZ +
|
||||
(PT.IGNORE_YAW if yaw_rate_flag else PT.IGNORE_YAW_RATE),
|
||||
velocity=vector_local.vector,
|
||||
yaw=euler_from_orientation(pose_local.pose.orientation)[2] - math.pi / 2,
|
||||
yaw_rate=req.yaw_rate)
|
||||
return position_pub, msg
|
||||
|
||||
elif isinstance(req, srv.SetAttitudeRequest):
|
||||
ps.header.frame_id = req.frame_id or 'local_origin'
|
||||
ps.header.frame_id = req.frame_id or LOCAL_FRAME
|
||||
ps.pose.orientation = orientation_from_euler(req.roll, req.pitch, req.yaw)
|
||||
pose_local = tf_buffer.transform(ps, 'local_origin', TRANSFORM_TIMEOUT)
|
||||
pose_local = tf_buffer.transform(ps, LOCAL_FRAME, TRANSFORM_TIMEOUT)
|
||||
msg = AttitudeTarget(orientation=pose_local.pose.orientation,
|
||||
thrust=req.thrust,
|
||||
type_mask=AttitudeTarget.IGNORE_YAW_RATE + AttitudeTarget.IGNORE_PITCH_RATE +
|
||||
AttitudeTarget.IGNORE_ROLL_RATE)
|
||||
return attitude_pub, msg
|
||||
|
||||
elif isinstance(req, srv.SetAttitudeYawRateRequest):
|
||||
msg = AttitudeTarget(orientation=orientation_from_euler(req.roll, req.pitch, 0),
|
||||
thrust=req.thrust,
|
||||
type_mask=AttitudeTarget.IGNORE_PITCH_RATE + AttitudeTarget.IGNORE_ROLL_RATE)
|
||||
msg.body_rate.z = req.yaw_rate
|
||||
return attitude_pub, msg
|
||||
|
||||
elif isinstance(req, srv.SetRatesYawRequest):
|
||||
ps.header.frame_id = req.frame_id or 'local_origin'
|
||||
ps.pose.orientation = orientation_from_euler(0, 0, req.yaw)
|
||||
pose_local = tf_buffer.transform(ps, 'local_origin', TRANSFORM_TIMEOUT)
|
||||
msg = AttitudeTarget(orientation=pose_local.pose.orientation,
|
||||
thrust=req.thrust,
|
||||
type_mask=AttitudeTarget.IGNORE_YAW_RATE,
|
||||
body_rate=Vector3(req.roll_rate, req.pitch_rate, 0))
|
||||
type_mask=AT.IGNORE_YAW_RATE + AT.IGNORE_PITCH_RATE + AT.IGNORE_ROLL_RATE)
|
||||
return attitude_pub, msg
|
||||
|
||||
elif isinstance(req, srv.SetRatesRequest):
|
||||
@@ -311,10 +261,23 @@ def handle(req):
|
||||
rospy.logwarn('No connection to the FCU')
|
||||
return {'message': 'No connection to the FCU'}
|
||||
|
||||
if isinstance(req, srv.NavigateRequest) and req.speed <= 0:
|
||||
if isinstance(req, (srv.NavigateRequest, srv.NavigateGlobalRequest)) and req.speed <= 0:
|
||||
rospy.logwarn('Navigate speed must be greater than zero, %s passed')
|
||||
return {'message': 'Navigate speed must be greater than zero, %s passed' % req.speed}
|
||||
|
||||
if isinstance(req, (srv.NavigateRequest, srv.NavigateGlobalRequest)) and \
|
||||
(pose is None or rospy.get_rostime() - pose.header.stamp > LOCAL_POSITION_TIMEOUT):
|
||||
rospy.logwarn('No local position')
|
||||
return {'message': 'No local position'}
|
||||
|
||||
if getattr(req, 'yaw_rate', 0) != 0 and not math.isnan(getattr(req, 'yaw')):
|
||||
rospy.logwarn('Yaw value should be NaN for setting yaw rate')
|
||||
return {'message': 'Yaw value should be NaN for setting yaw rate'}
|
||||
|
||||
if math.isnan(getattr(req, 'yaw', 0)) and math.isnan(getattr(req, 'yaw_rate', 0)):
|
||||
rospy.logwarn('Both yaw and yaw_rate cannot be NaN')
|
||||
return {'message': 'Both yaw and yaw_rate cannot be NaN'}
|
||||
|
||||
try:
|
||||
with handle_lock:
|
||||
stamp = rospy.get_rostime()
|
||||
@@ -340,6 +303,25 @@ def handle(req):
|
||||
return {'success': False, 'message': str(e)}
|
||||
|
||||
|
||||
def land(req):
|
||||
if not state or not state.connected:
|
||||
rospy.logwarn('No connection to the FCU')
|
||||
return {'message': 'No connection to the FCU'}
|
||||
|
||||
rospy.loginfo('Set %s mode', LAND_MODE)
|
||||
res = set_mode(custom_mode=LAND_MODE)
|
||||
if not res.mode_sent:
|
||||
return {'message': 'Cannot send %s mode request' % LAND_MODE}
|
||||
|
||||
start = rospy.get_rostime()
|
||||
while True:
|
||||
if state.mode == LAND_MODE:
|
||||
return {'success': True}
|
||||
if rospy.get_rostime() - start > LAND_TIMEOUT:
|
||||
return {'message': '%s mode request timed out' % LAND_MODE}
|
||||
rospy.sleep(0.1)
|
||||
|
||||
|
||||
def release(req):
|
||||
global current_pub
|
||||
current_pub = None
|
||||
@@ -348,22 +330,19 @@ def release(req):
|
||||
|
||||
|
||||
rospy.Service('navigate', srv.Navigate, handle)
|
||||
rospy.Service('navigate_global', srv.NavigateGlobal, handle)
|
||||
rospy.Service('set_position', srv.SetPosition, handle)
|
||||
rospy.Service('set_position/yaw_rate', srv.SetPositionYawRate, handle)
|
||||
rospy.Service('set_position_global', srv.SetPositionGlobal, handle)
|
||||
rospy.Service('set_position_global/yaw_rate', srv.SetPositionGlobalYawRate, handle)
|
||||
rospy.Service('set_velocity', srv.SetVelocity, handle)
|
||||
rospy.Service('set_velocity/yaw_rate', srv.SetVelocityYawRate, handle)
|
||||
rospy.Service('set_attitude', srv.SetAttitude, handle)
|
||||
rospy.Service('set_attitude/yaw_rate', srv.SetAttitudeYawRate, handle)
|
||||
rospy.Service('set_rates', srv.SetRates, handle)
|
||||
rospy.Service('set_rates/yaw', srv.SetRatesYaw, handle)
|
||||
rospy.Service('land', Trigger, land)
|
||||
rospy.Service('release', Trigger, release)
|
||||
|
||||
|
||||
def get_telemetry(req):
|
||||
res = {
|
||||
'frame_id': req.frame_id or 'local_origin',
|
||||
'frame_id': req.frame_id or LOCAL_FRAME,
|
||||
'x': float('nan'),
|
||||
'y': float('nan'),
|
||||
'z': float('nan'),
|
||||
@@ -381,7 +360,7 @@ def get_telemetry(req):
|
||||
'voltage': float('nan'),
|
||||
'cell_voltage': float('nan')
|
||||
}
|
||||
frame_id = req.frame_id or 'local_origin'
|
||||
frame_id = req.frame_id or LOCAL_FRAME
|
||||
stamp = rospy.get_rostime()
|
||||
|
||||
if pose:
|
||||
@@ -404,7 +383,10 @@ def get_telemetry(req):
|
||||
res['vx'] = linear.vector.x
|
||||
res['vy'] = linear.vector.y
|
||||
res['vz'] = linear.vector.z
|
||||
# TODO pitch_rate, roll_rate, yaw_rate
|
||||
|
||||
res['yaw_rate'] = velocity.twist.angular.z
|
||||
res['pitch_rate'] = velocity.twist.angular.y
|
||||
res['roll_rate'] = velocity.twist.angular.x
|
||||
|
||||
if global_position and stamp - global_position.header.stamp < rospy.Duration(5):
|
||||
res['lat'] = global_position.latitude
|
||||
@@ -441,7 +423,8 @@ def start_loop():
|
||||
try:
|
||||
stamp = rospy.get_rostime()
|
||||
|
||||
if getattr(current_req, 'update_frame', False) or isinstance(current_req, srv.NavigateRequest):
|
||||
if getattr(current_req, 'update_frame', False) or \
|
||||
isinstance(current_req, (srv.NavigateRequest, srv.NavigateGlobalRequest)):
|
||||
current_pub, current_msg = get_publisher_and_message(current_req, stamp, True,
|
||||
getattr(current_req, 'update_frame', False))
|
||||
|
||||
@@ -451,7 +434,7 @@ def start_loop():
|
||||
# For monitoring
|
||||
if isinstance(current_msg, PositionTarget):
|
||||
p = PoseStamped()
|
||||
p.header.frame_id = 'local_origin'
|
||||
p.header.frame_id = LOCAL_FRAME
|
||||
p.header.stamp = stamp
|
||||
p.pose.position = current_msg.position
|
||||
p.pose.orientation = orientation_from_euler(0, 0, current_msg.yaw + math.pi / 2)
|
||||
|
||||
@@ -2,6 +2,7 @@ float32 x
|
||||
float32 y
|
||||
float32 z
|
||||
float32 yaw
|
||||
float32 yaw_rate
|
||||
float32 speed
|
||||
string frame_id
|
||||
bool update_frame
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
float32 lat
|
||||
float32 lon
|
||||
float32 z
|
||||
float32 yaw
|
||||
float32 yaw_rate
|
||||
float32 speed
|
||||
string frame_id
|
||||
bool update_frame
|
||||
bool auto_arm
|
||||
@@ -1,8 +0,0 @@
|
||||
float32 roll
|
||||
float32 pitch
|
||||
float32 yaw_rate
|
||||
float32 thrust
|
||||
bool auto_arm
|
||||
---
|
||||
bool success
|
||||
string message
|
||||
@@ -2,6 +2,7 @@ float32 x
|
||||
float32 y
|
||||
float32 z
|
||||
float32 yaw
|
||||
float32 yaw_rate
|
||||
string frame_id
|
||||
bool update_frame
|
||||
bool auto_arm
|
||||
|
||||
@@ -2,6 +2,7 @@ float32 lat
|
||||
float32 lon
|
||||
float32 z
|
||||
float32 yaw
|
||||
float32 yaw_rate
|
||||
string frame_id
|
||||
bool update_frame
|
||||
bool auto_arm
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
float32 x
|
||||
float32 y
|
||||
float32 z
|
||||
float32 yaw_rate
|
||||
string frame_id
|
||||
bool update_frame
|
||||
bool auto_arm
|
||||
---
|
||||
bool success
|
||||
string message
|
||||
@@ -1,10 +0,0 @@
|
||||
float32 pitch_rate
|
||||
float32 roll_rate
|
||||
float32 yaw
|
||||
float32 thrust
|
||||
string frame_id
|
||||
bool update_frame
|
||||
bool auto_arm
|
||||
---
|
||||
bool success
|
||||
string message
|
||||
@@ -2,6 +2,7 @@ float32 vx
|
||||
float32 vy
|
||||
float32 vz
|
||||
float32 yaw
|
||||
float32 yaw_rate
|
||||
string frame_id
|
||||
bool update_frame
|
||||
bool auto_arm
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
float32 vx
|
||||
float32 vy
|
||||
float32 vz
|
||||
float32 yaw_rate
|
||||
string frame_id
|
||||
bool update_frame
|
||||
bool auto_arm
|
||||
---
|
||||
bool success
|
||||
string message
|
||||
30
docs/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
Клевер
|
||||
======
|
||||
|
||||
<img src="assets/clever3.png" align="right" width="300px" alt="Клевер">
|
||||
|
||||
«Клевер» — это учебный конструктор программируемого квадрокоптера, состоящего из популярных открытых компонентов, а также набор необходимой документации и библиотек для работы с ним.
|
||||
|
||||
Набор включает в себя полетный контроллер PixHawk/PixRacer с полетным стеком PX4, Raspberry Pi 3 в качестве управлящего бортового компьютера, модуль камеры для реализации полетов с использованием компьютерного зрения, а также набор различных датчиков и другой периферии.
|
||||
|
||||
На базе точно такой же платформы были созданы многие «большие» проекты компании Copter Express, например, дроны для [пиар-акций по автономной доставке пиццы](https://www.youtube.com/watch?v=hmkAoZOtF58) (Самара, Казань); дрон-доставщик кофе в Сколково, мониторинговый дрон с зарядной станцией, дроны-победители на полевых испытаниях «[Робокросс-2016](https://www.youtube.com/watch?v=dGbDaz_VmYU)», «[Робокросс-2017](https://youtu.be/AQnd2CRczbQ)» и многие другие.
|
||||
|
||||
Для того, чтобы научиться собирать, настраивать, пилотировать и программировать автономный дрон «Клевер», воспользуйтесь этим учебником.
|
||||
|
||||
Образ для Raspberry Pi
|
||||
----------------------
|
||||
|
||||
**Образ ОС** для RPi 3 с предустановленным и преднастроенным ПО можно скачать [здесь](microsd_images.html).
|
||||
|
||||
Образ включает в себя:
|
||||
|
||||
* Raspbian Stretch
|
||||
* ROS Kinetic
|
||||
* Настроенную работу с сетью
|
||||
* OpenCV
|
||||
* mavros
|
||||
* Набор ПО для работы с Клевером
|
||||
|
||||
[Описание API](simple_offboard.html) для автономных полетов.
|
||||
|
||||
Исходный код сборщика образа и всего ПО можно найти на [GitHub](https://github.com/CopterExpress/clever).
|
||||
38
docs/SUMMARY.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Summary
|
||||
|
||||
* [Введение](README.md)
|
||||
* [Сборка](assemble.md)
|
||||
* [Первоначальная настройка](setup.md)
|
||||
* [Полетные режимы](modes.md)
|
||||
* [Raspberry Pi](raspberry.md)
|
||||
* [Образ операционной системы на RPi](microsd_images.md)
|
||||
* [Подключение Raspberry Pi к PixHawk](connection.md)
|
||||
* [Подключение по Wi-Fi](wifi.md)
|
||||
* [Работа с QGroundControl через Wi-Fi](gcs_bridge.md)
|
||||
* [SSH-доступ](ssh.md)
|
||||
* [Неисправности радиоаппаратуры](radioerrors.md)
|
||||
* [Безопасность](safety.md)
|
||||
* [Техника безопасности по пайке](tb.md)
|
||||
* [Просмотр видеострима с камер](web_video_server.md)
|
||||
* [Работа с ROS](ros.md)
|
||||
* [MAVROS](mavros.md)
|
||||
* [Автономный полет в OFFBOARD](simple_offboard.md)
|
||||
* [Примеры программ](snippets.md)
|
||||
* [Навигация по ArUco-маркерам](aruco.md)
|
||||
* [Взаимодействие с Arduino](arduino.md)
|
||||
* [Системы координат](frames.md)
|
||||
* [Работа с камерой \(компьютерное зрение\)](camera.md)
|
||||
* [Ориентация камеры](camera_frame.md)
|
||||
* [Визуализация с помощью rviz](rviz.md)
|
||||
* [Работа с SITL](sitl.md)
|
||||
* [Подключение GPS](gps.md)
|
||||
* [Использование 3G-модема](3g.md)
|
||||
* Учебник
|
||||
* [Уроки](lessons.md)
|
||||
* [Учебно-методическое пособие](metod.md)
|
||||
* [Контрольные и проверочные материалы](tests.md)
|
||||
* [Другое](drugoe.md)
|
||||
* [CopterHack-2017](copterhack2017.md)
|
||||
* [Прошивка ESC контроллеров с помощью Arduino](esc_firmware.md)
|
||||
* [Работа со светодиодной лентой](leds.md)
|
||||
* [Полезные ссылки](links.md)
|
||||
@@ -33,10 +33,34 @@ roslaunch clever arduino.launch
|
||||
sudo systemctl restart clever
|
||||
```
|
||||
|
||||
Задержки
|
||||
---
|
||||
|
||||
При использовании `rosserial_arduino` микроконтроллер Arduino не должен быть заблокирован больше чем на несколько секунд (например, с использованием функции `delay`); иначе связь между Raspberry Pi и Arduino будет разорвана.
|
||||
|
||||
При реализации долгих циклов `while` обеспечьте периодический вызов функции `hn.spinOnce`:
|
||||
|
||||
```cpp
|
||||
while(/* условие */) {
|
||||
// ... Произвести необходимые действия
|
||||
nh.spinOnce();
|
||||
}
|
||||
```
|
||||
|
||||
Для огранизации долгих задержек используйте задержки в цикле с периодическим вызовом функции `hn.spinOnce()`:
|
||||
|
||||
```cpp
|
||||
// Задержка на 8 секунд
|
||||
for(int i=0; i<8; i++) {
|
||||
delay(1000);
|
||||
nh.spinOnce();
|
||||
}
|
||||
```
|
||||
|
||||
Работа с Клевером
|
||||
---
|
||||
|
||||
Набор сервисов и топиков аналогичен обычному набору в [simple_offboard](/docs/simple_offboard.md) и [mavros](/docs/mavros.md).
|
||||
Набор сервисов и топиков аналогичен обычному набору в [simple_offboard](simple_offboard.md) и [mavros](mavros.md).
|
||||
|
||||
Пример программы, контролирующей коптер по позиции, с использованием сервисов `navigate` и `set_mode`:
|
||||
|
||||
@@ -90,8 +114,11 @@ void setup()
|
||||
navigate.call(nav_req, nav_res);
|
||||
|
||||
// Ждем 5 секунд
|
||||
delay(5000);
|
||||
|
||||
for(int i=0; i<5; i++) {
|
||||
delay(1000);
|
||||
nh.spinOnce();
|
||||
}
|
||||
|
||||
nav_req.auto_arm = false;
|
||||
|
||||
// Пролет вперед на 3 метра:
|
||||
@@ -105,7 +132,10 @@ void setup()
|
||||
navigate.call(nav_req, nav_res);
|
||||
|
||||
// Ждем 5 секунд
|
||||
delay(5000);
|
||||
for(int i=0; i<5; i++) {
|
||||
delay(1000);
|
||||
nh.spinOnce();
|
||||
}
|
||||
|
||||
// Полет в точку 1:0:2 по маркерному полю
|
||||
nh.loginfo("Fly on point");
|
||||
@@ -119,7 +149,10 @@ void setup()
|
||||
navigate.call(nav_req, nav_res);
|
||||
|
||||
// Ждем 5 секунд
|
||||
delay(5000);
|
||||
for(int i=0; i<5; i++) {
|
||||
delay(1000);
|
||||
nh.spinOnce();
|
||||
}
|
||||
|
||||
// Посадка
|
||||
nh.loginfo("Land");
|
||||
@@ -132,6 +165,45 @@ void loop()
|
||||
}
|
||||
```
|
||||
|
||||
Получение телеметрии
|
||||
---
|
||||
|
||||
С Arduino можно использовать [сервис](simple_offboard.md) `get_telemetry`. Для этого надо объявить его по аналогии с сервисами `navigate` и `set_mode`:
|
||||
|
||||
```cpp
|
||||
#include <ros.h>
|
||||
|
||||
// ...
|
||||
|
||||
#include <clever/GetTelemetry.h>
|
||||
|
||||
// ...
|
||||
|
||||
ros::ServiceClient<GetTelemetry::Request, GetTelemetry::Response> getTelemetry("/get_telemetry");
|
||||
|
||||
// ...
|
||||
|
||||
nh.serviceClient(getTelemetry);
|
||||
|
||||
// ...
|
||||
|
||||
GetTelemetry::Request gt_req;
|
||||
GetTelemetry::Response gt_res;
|
||||
|
||||
|
||||
// ...
|
||||
|
||||
gt_req.frame_id = "aruco_map"; // фрейм для значений x, y, z
|
||||
getTelemetry.call(gt_req, gt_res);
|
||||
|
||||
// gt_res.x - положение коптера по x
|
||||
// gt_res.y - положение коптера по y
|
||||
// gt_res.z - положение коптера по z
|
||||
```
|
||||
|
||||
Проблемы
|
||||
---
|
||||
|
||||
При использовании Arudino Nano может не хватать оперативной памяти (RAM). В таком случае в Aruino IDE будут появляться сообщения, типа:
|
||||
|
||||
```
|
||||
@@ -144,3 +216,16 @@ void loop()
|
||||
```cpp
|
||||
#define __AVR_ATmega168__ 1
|
||||
```
|
||||
|
||||
Можно уменьшить количество занятой памяти еще сильнее, если вручную настроить количество publisher'ов и subscriber'ов, а также размеры буферов памяти, выделяемой для сообщений, например:
|
||||
|
||||
```cpp
|
||||
#include <ros.h>
|
||||
|
||||
// ...
|
||||
|
||||
typedef ros::NodeHandle_<ArduinoHardware, 3, 3, 100, 100> NodeHandle;
|
||||
|
||||
// ...
|
||||
NodeHandle nh;
|
||||
```
|
||||
@@ -1,23 +1,25 @@
|
||||
Навигация с использованием ArUco-маркеров
|
||||
===
|
||||
# Навигация с использованием ArUco-маркеров
|
||||
|
||||
[ArUco-маркеры](https://docs.opencv.org/3.2.0/d5/dae/tutorial_aruco_detection.html) — это популярная технология для позиционирования
|
||||
[ArUco-маркеры](https://docs.opencv.org/3.2.0/d5/dae/tutorial_aruco_detection.html) — это популярная технология для позиционирования
|
||||
роботехнических систем с использованием компьютерного зрения.
|
||||
|
||||
Пример ArUco-маркеров:
|
||||
|
||||

|
||||

|
||||
|
||||
aruco_pose
|
||||
---
|
||||
> **Hint** При печати визуальных маркеров необходимо использовать максимально матовую бумагу. Глянцевая бумага будет бликовать на свету, сильно ухудшая качество распознавания.
|
||||
|
||||
Для быстрого генерирования маркеров для печати можно использовать онлайн-инструмент: http://chev.me/arucogen/.
|
||||
|
||||
## aruco\_pose
|
||||
|
||||
Модуль `aruco_pose` позволяет восстанавливать позицию коптера относительно карты ArUco-маркеров и сообщать ее полетному контролеру, используя механизм [Vision Position Estimation](https://dev.px4.io/en/ros/external_position_estimation.html).
|
||||
|
||||
При наличии источника положения коптера по маркерам появляется возможность производить точную автономную indoor-навигацию по позициям при помощи модуля [simple_offboard](/docs/simple_offboard.md).
|
||||
При наличии источника положения коптера по маркерам появляется возможность производить точную автономную indoor-навигацию по позициям при помощи модуля [simple\_offboard](simple_offboard.md).
|
||||
|
||||
### Включение
|
||||
|
||||
Необходимо убедиться, что в launch-файле Клевера (`~/catkin_ws/src/clever/clever/launch/clever.launch`) включен запуск aruco_pose и [камеры для компьютерного зрения](/docs/camera.md):
|
||||
Необходимо убедиться, что в launch-файле Клевера \(`~/catkin_ws/src/clever/clever/launch/clever.launch`\) включен запуск aruco\_pose и [камеры для компьютерного зрения](camera.md):
|
||||
|
||||
```xml
|
||||
<arg name="main_camera" default="true"/>
|
||||
@@ -44,25 +46,26 @@ sudo systemctl restart clever
|
||||
<param name="frame_id" value="aruco_map_raw"/>
|
||||
<!-- тип маркерного поля -->
|
||||
<param name="type" value="gridboard"/>
|
||||
|
||||
|
||||
<!-- количество маркеров по x -->
|
||||
<param name="markers_x" value="1"/>
|
||||
|
||||
|
||||
<!-- количество маркеров по y -->
|
||||
<param name="markers_y" value="6"/>
|
||||
|
||||
|
||||
<!-- ID маркера первого маркера (левого верхнего) -->
|
||||
<param name="first_marker" value="240"/>
|
||||
|
||||
<!-- длина стороны маркера в метрах -->
|
||||
<param name="markers_side" value="0.3362"/>
|
||||
|
||||
|
||||
<!-- растояние между маркерами -->
|
||||
<param name="markers_sep" value="0.46"/>
|
||||
</node>
|
||||
```
|
||||
|
||||
Можно задать отдельно расстояние между маркерами по горизонтали и вертикали:
|
||||
|
||||
```xml
|
||||
<!-- расстояние между маркерами по горизонтали -->
|
||||
<param name="markers_sep_x" value="0.97"/>
|
||||
@@ -79,13 +82,13 @@ sudo systemctl restart clever
|
||||
|
||||
Нумерация маркеров ведется с левого верхнего угла поля.
|
||||
|
||||
Для контроля карты, по которой в данный момент коптер осуществляет навигацию, можно просмотреть содержимое топика `aruco_pose/map_image`. Через браузер его можно просмотреть при помощи [web_video_server](/docs/web_video_server.md) по ссылке http://192.168.11.1:8080/snapshot?topic=/aruco_pose/map_image:
|
||||
Для контроля карты, по которой в данный момент коптер осуществляет навигацию, можно просмотреть содержимое топика `aruco_pose/map_image`. Через браузер его можно просмотреть при помощи [web\_video\_server](web_video_server.md) по ссылке [http://192.168.11.1:8080/snapshot?topic=/aruco\_pose/map\_image](http://192.168.11.1:8080/snapshot?topic=/aruco_pose/map_image):
|
||||
|
||||

|
||||

|
||||
|
||||
При полетах необходимо убедиться, что наклеенные на пол метки соответствуют карте.
|
||||
|
||||
В топике `aruco_pose/debug` (http://192.168.11.1:8080/snapshot?topic=/aruco_pose/debug) доступен текущий результат распознования меток:
|
||||
В топике `aruco_pose/debug` \([http://192.168.11.1:8080/snapshot?topic=/aruco\_pose/debug](http://192.168.11.1:8080/snapshot?topic=/aruco_pose/debug)\) доступен текущий результат распознования меток:
|
||||
|
||||
TODO
|
||||
|
||||
@@ -93,21 +96,21 @@ TODO
|
||||
|
||||
По [соглашению](http://www.ros.org/reps/rep-0103.html), в маркерном поле используется стандартная система координат ENU:
|
||||
|
||||
* x — вправо (условный "восток");
|
||||
* y — вперед (условный "север");
|
||||
* x — вправо \(условный "восток"\);
|
||||
* y — вперед \(условный "север"\);
|
||||
* z — вверх.
|
||||
|
||||
_Примечание_: указанное выше определение приведено для ситуации, когда поле маркеров лежит на полу.
|
||||
|
||||
Таким образом, нулевой является левая нижня точка маркерного поля. Угол по рысканью считается равным 0, когда коптер смотрит направо (по оси x).
|
||||
Таким образом, нулевой является левая нижня точка маркерного поля. Угол по рысканью считается равным 0, когда коптер смотрит направо \(по оси x\).
|
||||
|
||||

|
||||

|
||||
|
||||
### Настройка полетного контролера
|
||||
|
||||
Для правильной работы Vision Position Estimation необходимо (через [QGroundControl](/docs/gcs_bridge.md)) убедиться, что:
|
||||
Для правильной работы Vision Position Estimation необходимо \(через [QGroundControl](gcs_bridge.md)\) убедиться, что:
|
||||
|
||||
* Для PixHawk: Установлена прошивка с LPE (local position estimator). Для PixRacer: параметр `SYS_MC_EST_GROUP` установлен в `local_position_estimator, attitude_estimator_q`.
|
||||
* Для PixHawk: Установлена прошивка с LPE \(local position estimator\). Для PixRacer: параметр `SYS_MC_EST_GROUP` установлен в `local_position_estimator, attitude_estimator_q`.
|
||||
* В параметре `LPE_FUSION` включены **только** флажки `vision position`, `vision yaw`, `land detector`. Итоговое значение _28_.
|
||||
* Выключен компас: `ATT_W_MAG` = 0
|
||||
* Вес угла по рысканью по зрению: `ATT_W_EXT_HDG` = 0.5
|
||||
@@ -118,13 +121,16 @@ _Примечание_: указанное выше определение пр
|
||||
* `LNDMC_ROT_MAX` = 45 deg
|
||||
* `LNDMC_THR_RANGE` = 0.5
|
||||
* `LNDMC_Z_VEL_MAX` = 1 m/s
|
||||
|
||||
|
||||
Для простоты настройки можно воспользоваться готовым файлом настроек для [Clever 2](https://github.com/CopterExpress/clever/blob/masterassets/Clever2LPE_160118.params) или для [Clever 3](https://github.com/CopterExpress/clever/blob/masterassets/Clever3_LPE_020218.params) и вгрузить его в контроллер с помощью меню Tools - Load from file из раздела Parameters в QGroundControl.
|
||||
|
||||

|
||||
|
||||
### Полет
|
||||
|
||||
При правильной настройке коптер начнет удерживать позицию по VPE (в [режимах](/docs/modes.md) `POSCTL` или `OFFBOARD`) автоматически.
|
||||
При правильной настройке коптер начнет удерживать позицию по VPE \(в [режимах](modes.md) `POSCTL` или `OFFBOARD`\) автоматически.
|
||||
|
||||
Для [автономных полетов](/docs/simple_offboard.md) можно будет использовать функции `navigate`, `set_position`, `set_velocity`. Для полета в определенные координаты маркерного поля необходимо использовать фрейм `aruco_map`:
|
||||
Для [автономных полетов](simple_offboard.md) можно будет использовать функции `navigate`, `set_position`, `set_velocity`. Для полета в определенные координаты маркерного поля необходимо использовать фрейм `aruco_map`:
|
||||
|
||||
```python
|
||||
# Вначале необходимо взлететь, чтобы коптер увидел карту меток
|
||||
@@ -136,15 +142,16 @@ time.sleep(5)
|
||||
# Полет в координату 2:2 маркерного поля, высота 2 метра
|
||||
navigate(2, 2, 2, speed=1, frame_id='aruco_map', update_frame=True) # полет в координату 2:2, высота 3 метра
|
||||
```
|
||||
См. [другие функции](/docs/simple_offboard.md) simple offboard.
|
||||
|
||||
См. [другие функции](simple_offboard.md) simple offboard.
|
||||
|
||||
### Расположение маркеров на потолке
|
||||
|
||||
> **Info** Образ версии >0.2.
|
||||
> **Info** Образ версии >0.2.
|
||||
|
||||

|
||||

|
||||
|
||||
Для навигации по маркерам, расположенным на потолке, необходимо поставить основную камеру так, чтобы она смотрела вверх и [установить соответствующий фрейм камеры](/docs/camera_frame.md).
|
||||
Для навигации по маркерам, расположенным на потолке, необходимо поставить основную камеру так, чтобы она смотрела вверх и [установить соответствующий фрейм камеры](camera_frame.md).
|
||||
|
||||
Чтобы задавать карту маркеров в "перевернутой" системе координат, необходимо изменить параметр `aruco_orientation` в файле `~/catkin_ws/src/clever/clever/aruco.launch`:
|
||||
|
||||
@@ -152,8 +159,11 @@ navigate(2, 2, 2, speed=1, frame_id='aruco_map', update_frame=True) # поле
|
||||
<param name="aruco_orientation" value="local_origin_upside_down"/>
|
||||
```
|
||||
|
||||
При задании вышеуказанного параметра фрейм aruco_map также окажется "перевернутым". Таким образом, для полета на высоту 2 метра ниже потолка, аргумент `z` нужно устанавливать в 2:
|
||||
При задании вышеуказанного параметра фрейм aruco\_map также окажется "перевернутым". Таким образом, для полета на высоту 2 метра ниже потолка, аргумент `z` нужно устанавливать в 2:
|
||||
|
||||
```python
|
||||
navigate(x=1, y=2, z=1.1, speed=0.5, frame_id='aruco_map')
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
175
docs/assemble.md
@@ -1,12 +1,12 @@
|
||||
Инструкция по сборке конструктора Клевер 2
|
||||
==========================================
|
||||
|
||||

|
||||

|
||||
|
||||
Состав конструктора
|
||||
-------------------
|
||||
|
||||

|
||||

|
||||
|
||||
* Рама центральная x2.
|
||||
* Рама дополнительная х4.
|
||||
@@ -14,14 +14,14 @@
|
||||
* Ножки x8.
|
||||
* Защита для лучей x8.
|
||||
* Защита пропеллеров x16.
|
||||
* Защита боковая x16.
|
||||
* Защита боковая x16.
|
||||
* Пропеллер пластиковый Dalprop 5045 x4.
|
||||
* Бесколлекторный электродвигатель Racerstar BR2205 2300kV x4.
|
||||
* Регуляторы хода ESC, DYS XSD20А x4.
|
||||
* Разъем силовой XT60 pin x1.
|
||||
* Разъем силовой XT60 socket x1.
|
||||
* Трехпроводной шлейф “мама-мама” x2.
|
||||
* Провод медный многожильный с силиконовой изоляцией 14AWG (красный, черный), длина 50 см.
|
||||
* Провод медный многожильный с силиконовой изоляцией 14AWG (красный, черный), длина 50 см.
|
||||
* Плата распределения питания PDB BeeRotor Power Distribution Board V2.0 x1.
|
||||
* Аккумуляторная батарея (АКБ) Li-ion 18650 x8.
|
||||
* Зарядное устройство EFEST Luc V4 Li-lon x1.
|
||||
@@ -33,7 +33,7 @@
|
||||
* Зарядное устройство EFEST LUC V4 x1.
|
||||
* Провод Micro USB - USB x1
|
||||
* Батарейный отсек 18650 li-Ion x1
|
||||
* Провод медный многожильный с силиконовой изоляцией 18AWG (красный, черный), длина 100 см.
|
||||
* Провод медный многожильный с силиконовой изоляцией 18AWG (красный, черный), длина 100 см.
|
||||
* Батарейка АА х4
|
||||
* Джампер, Bind-разъем
|
||||
|
||||
@@ -76,12 +76,12 @@
|
||||
16. Ручка A (VrA).
|
||||
17. Ручка B (VrB).
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
Дополнительное оборудование
|
||||
---------------------------
|
||||
#### Данное оборудование не входит в состав конструктора Клевер 2, но оно необходимо для реализации сборочного процесса
|
||||
#### Данное оборудование не входит в состав конструктора Клевер 2, но оно необходимо для реализации сборочного процесса
|
||||
|
||||
1. Паяльник
|
||||
2. Канифоль/ Флюс (нейтральный)
|
||||
@@ -92,10 +92,10 @@
|
||||
7. Канцелярский нож
|
||||
8. Мультиметр
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
[Техника безопасности при пайке](tb.md)
|
||||
|
||||
Порядок сборки
|
||||
--------------
|
||||
@@ -104,7 +104,7 @@
|
||||
|
||||
* Распаковать моторы. Используя плоскогубцы, укоротить провода на моторах, обрезать половину длины (оставив 25 мм).
|
||||
|
||||

|
||||

|
||||
|
||||
Зачистить
|
||||
* снять 2мм термоизоляции с конца провода не повредив медные жилы.
|
||||
@@ -112,41 +112,41 @@
|
||||
Скрутить провода.
|
||||
|
||||
Залудить
|
||||
* Нанести флюс на оголенную часть провода.
|
||||
* Нанести флюс на оголенную часть провода.
|
||||
* Покрыть припоем, используя пинцет.
|
||||
|
||||

|
||||

|
||||
|
||||
#### Закрепить мотор на луче
|
||||
|
||||
* Установить мотор на сторону луча с гравировкой.
|
||||
* Прикрепить моторы к лучам винтами М3х8, используя отвертку.
|
||||
|
||||

|
||||

|
||||
|
||||
* Лучи с моторами необходимо расположить согласно схеме. Стрелками указано направление вращения моторов.
|
||||
|
||||

|
||||
//указать стрелками направление вращения на моторах
|
||||

|
||||
|
||||
|
||||
#### Залудить три контактные площадки регулятора
|
||||
* Нанести флюс
|
||||
* Нанести флюс
|
||||
* Нанести припой
|
||||
|
||||
Чтобы припой аккуратно заполнил всю площадку, необходимо прогреть площадку регулятора. Для этого нужно удерживать жало паяльника на контактной плащадке в течение 2 сек (или больше, если потребуется)
|
||||
|
||||

|
||||

|
||||
* Повторить данную операцию для оставшихся трех регуляторов
|
||||
|
||||
#### Припаять провода моторов к регуляторам
|
||||
Припаять ранее приготовленные провода моторов к контактным площадкам регуляторов.
|
||||
|
||||

|
||||

|
||||
|
||||
* Повторить данную операцию для оставшихся трех регуляторов
|
||||
|
||||
### Монтаж разъемов питания
|
||||

|
||||
[Статья про силовые и управляющие цепи](powerConnection.md)
|
||||
|
||||
#### Подготовка проводов для силовых разъемов XT60
|
||||
|
||||
@@ -155,13 +155,13 @@
|
||||
* Длина 7 см (Для силового разъема XT60 pin) - 1 красный, 1 черный
|
||||
* Длина 9 см (Для силового разъема XT60 socket) - 1 красный, 1 черный
|
||||
|
||||

|
||||

|
||||
|
||||
#### Подготовка силовых разъемов питания XT60 pin и XT60 socket
|
||||
|
||||

|
||||
[Статья про силовые разъемы и их обозначения](connectortypes.md)
|
||||
|
||||

|
||||

|
||||
|
||||
1. Под разъем XT60 pin залудить два силовых провода красный и чёрный 14AWG длиной 7 см.
|
||||
2. Залудить контактные площадки разъема XT60 pin.
|
||||
@@ -171,51 +171,48 @@
|
||||
6. Надеть термоусадку ф5 на провода так, чтобы она закрывала контактные площадки проводов с XT60 .
|
||||
7. Усадить термоусадку феном.
|
||||
|
||||

|
||||

|
||||
|
||||
8. Повторить процедуру для разъема XT60 socket.
|
||||
|
||||
#### Подготовка разъема питания управляющей цепи 5В
|
||||
 // *почему используется именно 3х контактный разъем, почему шина питания посередине
|
||||
|
||||
1. Обрезать/вытащить все пины из одного из разъемов. Отсоединить его.
|
||||
2. Поддеть канцелярским ножом фиксатор на оставшемся разъеме, чтобы освободить 3-й провод.
|
||||
3. Убрать 3-й (оранжевый) провод из разъема, за ненадобностью.
|
||||
4. Длина оставшихся черного и красного проводов 10-12 см.
|
||||
|
||||
 *было бы круто, если делать такие картинки и в формате гифки
|
||||
 *было бы круто, если делать такие картинки и в формате гифки
|
||||
|
||||
### Монтаж платы распределения питания
|
||||
|
||||

|
||||
|
||||
#### Предпаячная проверка
|
||||
|
||||

|
||||
[Статья про прозвонку](testConnection.md)
|
||||
|
||||

|
||||

|
||||
|
||||
Прозвонить следующие цепи на НЕЗАМКНУТОСТЬ (отсутствие звукового сигнала мультиметра):
|
||||
* “BAT+” и “BAT-”
|
||||
* “BAT+” и “BAT-”
|
||||
* “12V” и “GND”
|
||||
* “5V” и “GND”
|
||||
|
||||
Прозвонить следующие цепи на ЗАМКНУТОСТЬ (появление звукового сигнала мультиметра):
|
||||
* “BAT-” c каждым контактом, обозначенным “-” и “GND”
|
||||
* “BAT-” c каждым контактом, обозначенным “-” и “GND”
|
||||
* “BAT+”, с каждым контактом, обозначенным “+”
|
||||
|
||||
#### Залудить контактные площадки платы питания
|
||||
1.  контактные площадки платы питания.
|
||||
1. [Залудить*](zap.md) контактные площадки платы питания.
|
||||
2. С помощью мультиметра проверить отсутствие контактного замыкания на плате (прозвонить)
|
||||
|
||||

|
||||

|
||||
|
||||
Чтобы припой аккуратно заполнил всю площадку, необходимо её прогреть. Для этого нужно удерживать жало паяльника на контактной плащадке в течение 2 сек (или больше, если потребуется)
|
||||
|
||||
#### Пайка силового разъема питания XT60
|
||||
Припаять разъем для АКБ, соблюдая полярность на контактных площадках.
|
||||
|
||||

|
||||

|
||||
|
||||
ВАЖНО о полярности
|
||||
* красный провод - это “+”
|
||||
@@ -225,12 +222,12 @@
|
||||
Припаять разъем 5В, соблюдая полярность на контактных площадках.
|
||||
(на изображении: красный провод - это питание “+”)
|
||||
|
||||

|
||||

|
||||
|
||||
### Монтаж отсека АКБ
|
||||
|
||||
#### Подготовка перемычек (3 шт.)
|
||||

|
||||

|
||||
|
||||
* Отрезать силовой провод длиной 2 см.
|
||||
* Зачистить с обеих сторон.
|
||||
@@ -240,7 +237,7 @@
|
||||
* Прозвонить мультиметром. В случае необходимости зачистить наждачной бумагой.
|
||||
|
||||
#### Подготовка отсека АКБ
|
||||

|
||||

|
||||
* Приклеить наклейки с разметкой внутрь отсека АКБ, в соответствии с полярностью.
|
||||
* Приклеить ленту из скотча на дно отсека.
|
||||
|
||||
@@ -248,34 +245,34 @@
|
||||
### Монтаж платы распределения питания
|
||||
|
||||
* Установить плату питания на раму винтами М3х8 и пластиковыми гайками.
|
||||

|
||||

|
||||
|
||||
* ВАЖНО
|
||||
Стрелочка на плате направлена в сторону носового выреза
|
||||

|
||||

|
||||
|
||||
|
||||
#### Монтаж элементов
|
||||
|
||||
1. Установить гайки в пластиковые держатели
|
||||

|
||||

|
||||
|
||||
2. Установить лучи на раму винтами М3х16
|
||||
*Лучи устанавливаются поверх рамы
|
||||
*Лучи устанавливаются поверх рамы
|
||||
*Пластиковые держатели устанавливаются снизу рамы
|
||||

|
||||

|
||||
|
||||
3. Расположение моторов
|
||||
Проверить расположение моторов (моторы с черной гайкой в левом верхнем углу и в правом нижнем).
|
||||

|
||||

|
||||
|
||||
4. Продеть силовые провода регуляторов в отверстия.
|
||||

|
||||

|
||||
|
||||
|
||||
#### Пайка силовой цепи платы питания
|
||||
Припаять силовые провода регуляторов к плате питания, соблюдая полярность.
|
||||

|
||||

|
||||
|
||||
ВАЖНО о полярности
|
||||
*красный провод - это “+”
|
||||
@@ -284,73 +281,74 @@
|
||||
|
||||
### Сопряжение приемника и пульта
|
||||
1. Подключить радиоприемник к разъему 5В. В любой разъем, GND внизу. На схеме питание обозначено как 5V
|
||||

|
||||

|
||||
3. Подключить АКБ.
|
||||
Светодиод на радиоприемнике должен мигать.
|
||||

|
||||

|
||||
|
||||
|
||||
#### БЕЗОПАСНОСТЬ при работе с АКБ
|
||||

|
||||

|
||||
|
||||
#### Включение радиопульта
|
||||
1. На пульте зажать кнопку BIND KEY.
|
||||
2. Включить пульт (перещелкнуть POWER, BIND KEY не отпускаем).
|
||||
3. Ждем синхронизацию.
|
||||
4. Отсоединить джампер.
|
||||
5. Светодиод горит непрерывно.
|
||||
1. Вставить джампер в B/VCC радиоприемника (замкнуть "землю" и "сигнал")
|
||||
2. На пульте зажать кнопку BIND KEY.
|
||||
3. Включить пульт (перещелкнуть POWER, BIND KEY не отпускаем).
|
||||
4. Подключить аккумулятор к коптеру.
|
||||
5. Ждем синхронизацию.
|
||||
6. Отсоединить джампер.
|
||||
7. Светодиод горит непрерывно.
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
[Мануал по неисправностям радиоаппаратуры](radioerrors1.md)
|
||||
### Проверка направления вращения моторов
|
||||
1. Наклеить наклейки на АКБ 18650.
|
||||
2. Установить 18650 в отсек АКБ, соблюдая полярность.
|
||||
|
||||

|
||||

|
||||
|
||||
3. Проверить, что разъем питания 5В подключен к приемнику по схеме.
|
||||
4. Подключить регулятор мотора к 3 каналу приемника CH3 по схеме.
|
||||

|
||||

|
||||
|
||||
5. Подключить внешнее питание (АКБ).
|
||||
6. Включить пульт.
|
||||
7. Подать левым стиком газ (throttle) на 10%.
|
||||
8. Проверить направления вращения мотора по схеме.
|
||||

|
||||

|
||||
|
||||
9. Если необходимо изменить направление вращения, то меняем любые два фазных провода мотора (нужно перепаять).
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
### Монтаж радиоприемника
|
||||
1. Установить пластиковые стойки 30 мм на раму винтами М3х8.
|
||||
2. Разъем питания 5В продеть в прорезь.
|
||||

|
||||

|
||||
|
||||
3. Приемник прикрепить к , используя двухсторонний скотч и ориентируясь на гравировку. Антенны направлены вперед.
|
||||
3. Приемник прикрепить к нижней дополнительной раме, используя двухсторонний скотч и ориентируясь на гравировку. Антенны направлены вперед.
|
||||
|
||||

|
||||

|
||||
|
||||
4. Установить 3х проводной шлейф в канал PPM / CH1.
|
||||

|
||||

|
||||
|
||||
5. Продеть в прорезь к разъему 5 В.
|
||||
6. Прикрутить нижнюю дополнительную раму к стойкам на центральной раме винтами М3х8.
|
||||

|
||||
6. Прикрутить нижнюю дополнительную раму к стойкам на центральной раме винтами М3х8.
|
||||

|
||||
|
||||
##### ВАЖНО
|
||||
Направление стрелок на плате питания и на раме дополнительной совпадают
|
||||
|
||||
Направление стрелок на плате питания и на дополнительной раме совпадают
|
||||
|
||||
### Монтаж полетного контроллера
|
||||
#### Переворачиваем сборку
|
||||

|
||||

|
||||
|
||||
#### Установка полетного контроллера Pixhawk
|
||||
1. Клеим 2х сторонний скотч по углам полетного контроллера
|
||||

|
||||
1. Клеим 2х сторонний скотч по углам полетного контроллера
|
||||

|
||||
|
||||
##### ВАЖНО
|
||||
При работе моторов возникают вибрации, отрицательно влияющие на показания датчиков полетного контроллера Pixhawk. Чтобы избежать этого эффекта, количество слоев двустороннего скотча
|
||||
@@ -358,7 +356,7 @@
|
||||
|
||||
2. Установить полетный контроллер в центр рамы
|
||||
|
||||

|
||||

|
||||
##### ВАЖНО
|
||||
Стрелки на раме и pixhawk должны быть сонаправлены
|
||||
|
||||
@@ -368,29 +366,29 @@
|
||||
2. Моторы к 1,2,3,4 портам MAIN OUT, согласно схеме
|
||||
3. Питание от PDB (5В/VCC) в любой порт, кроме SB (SBUS)
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
### Сборка регуляторов
|
||||
1. Клеим 2х сторонний скотч на основание защитного бокса регуляторов
|
||||

|
||||

|
||||
|
||||
2. Укладываем регуляторы в защитные боксы. Крепим полученную сборку к лучам рамы.
|
||||

|
||||

|
||||
|
||||
|
||||
### Установка защиты
|
||||
1. Закрепить нижнюю защиту винтами М3х16 на лучах рамы
|
||||

|
||||

|
||||
|
||||
2. Закрепить ножки к пластиковым держателям винтами М3х16
|
||||

|
||||

|
||||
|
||||
3. Закрепить стойки 30 мм в отверстия нижней защиты винтами М3х12
|
||||

|
||||

|
||||
|
||||
4. Закрепить верхнюю защиту винтами М3х12
|
||||

|
||||

|
||||
|
||||
|
||||
### Монтаж отсека АКБ
|
||||
@@ -402,11 +400,11 @@
|
||||
* Батарейный отсек (1 шт)
|
||||
|
||||
1. Прикрепить батарейный отсек на верхнюю дополнительную раму винтами М3х12 и гайками.
|
||||

|
||||

|
||||
|
||||
2. Прикрепить верхнюю дополнительную раму на стойки винтами М3х8.
|
||||
|
||||

|
||||

|
||||
|
||||
3. Установить АКБ в отсек.
|
||||
|
||||
@@ -415,12 +413,12 @@
|
||||
|
||||
1. Крепим антенны на 2х сторонний скотч или изоленту, а усики продеваем в передние отверстия верхней дополнительной рамы.
|
||||
|
||||

|
||||

|
||||
|
||||
Коптер готов к настройке!
|
||||
|
||||
|
||||
## БЕЗОПАСНОСТЬ ПРИ СБОРКЕ И НАСТРОЙКЕ
|
||||
## Безопасность при сборке и настройке
|
||||
|
||||
1. Снять пропеллеры.“Все наземные операции производить со снятыми пропеллерами. Устанавливать пропеллеры на моторы только перед полётом.”
|
||||
|
||||
@@ -430,17 +428,12 @@
|
||||
3. Позвать на помощь
|
||||
“Если при выполнении работ возникли какие-либо проблемы, необходимо обратиться к преподавателю или учителю, а не пытаться решить проблему самостоятельно.”
|
||||
|
||||

|
||||

|
||||
|
||||
## Безопасность при работе с Li-ion аккумуляторами 18650
|
||||
|
||||
1. Обращаться с аккумуляторами бережно. Не допускать падений, ударов деформаций.
|
||||
1. Обращаться с аккумуляторами бережно. Не допускать падений, ударов деформаций.
|
||||
2. При подключении (отключении) аккумуляторов держаться только за разъёмы, тянуть или дергать за провода запрещается.
|
||||
3. В случае обрыва разъемов, обнаружения нарушений целостности изоляции или корпуса аккумулятора, не трогая его, немедленно сообщить преподавателю.
|
||||
|
||||
|
||||
|
||||
|
||||
## 
|
||||
|
||||
|
||||
## [ТЕХНИКА БЕЗОПАСНОСТИ ПРИ ПАЙКЕ И ЛЁТНОЙ ЭКСПЛУАТАЦИИ КОПТЕРОВ](safety.md)
|
||||
|
||||
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |