DIY. Подводный дрон. История одного сумасшествия

В этом материале описано проектирование, разработка и сборка прототипа подводного дрона на базе Raspberry PI и управление им с Android-смартфона. Статья может пригодиться как новичкам (изложены азы управления электродвигателями, диодами, камерами, гироскопом), так и опытным инженерам (они могут поглумиться над исполнением и решениями :)). В общем, если вы любитель сделать чего-то электронного своими руками — милости прошу к прочтению.

Я пересмотрел много передач по Discovery про изобретателей, и однажды захотелось сделать самому что-то интересное, да чтобы во все тяжкие: электромоторы, контроллеры, управление, камера. Купив много полезных (и не очень) девайсов, я стал думать, какой же проект будет прикольно сделать. Пришел к выводу, что летающих дронов хоть чем-то жуй, ездящих тоже, а вот с водой какой-то косяк (ха-ха, да я «немного» ошибался).

После серфинга в интернете я нашел довольно много решений, но большинство из них либо еще не вышли в массы и находились в состоянии прототипов, либо стоили некисло дорого (3++к енотов). Если интересно, то можете глянуть пару хороших здесь.

Забегая наперед, скажу, что у меня вышел рабочий прототип, который способен плавать на нескольких метрах глубины, со всеми плюсами и минусами. Сложно сказать, что в результате я получил продукт, которым можно легко управлять и что он сейчас готов сделать что-то большее, чем унять инженерный азарт. Но все узлы у него работают исправно, и главное, что мои ошибки и наработки могут помочь кому-то сделать что-то значимое (даже если для только него самого).

Так как работы было проделано действительно много, можно разделить все по следующим темам:

В порядке бреда

Скажу сразу: я любитель, и большинство из того, что я делал, было сделано методом научного тыка, ибо собрать что-то и протестить на-а-амного веселее, чем просчитать и понять, что в теории оно не работает. Вариантов конструкции и идей было немалое количество, и были откровенно «глупые» реализации (одна из них будет описана чисто по приколу далее).

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

Как обычно, в начале приходят не самые умные идеи (а думать дважды — не мой подход), и я попытался поэкспериментировать с передачей момента магнитами. Выбрал простую конструкцию и собрал такой вот «высокотехнологичный» девайс:

На шестеренку было прикручено по два магнита и соединены с двигателями (соблюдая все полярности магнитов), между шестернями пластина которая имитирует стенки корпуса. Если у вас проснулось желание закрыть статью и выколоть себе глаза — это нормальная реакция на увиденное, но я вас предупреждал в начале :)

Несмотря на кривизну, после запуска макета можно смело прокричать: «И все-таки она вертится» ©. Момент передается, детали вращаются, но за счет притяжения сильно возрастает трение. Следующий минус, помимо десятков остальных, в том что если стопнуть воображаемый винт, то магниты теряют «контакт» и второй раз уже сцепиться не могут из-за разности скоростей.

Даже если собрать такую конструкцию, использовав подшипники, нормальные шестерни и магниты, это будет далеко не самым лучшим решением, ибо КПД, простота реализации и просто здравый смысл хромают.

В итоге принято решение не выпендриваться и взять бесколлекторный движок, и при надобности залить лаком обмотку. Вынести за корпус (да-да, в самую воду) и просто загерметизировать выведенные провода. Это будет значительно проще, чем думать конструкцию с маслом и сальниками или еще какой-то сложный механизм, для того, чтобы удержать воду вне механики и электроники.

Итак, перейдем к более серьезным вещам.

Выбор компонентов

Далее список того, что было использовано при создании аппарата.

Материнка

В качестве контроллера однозначно была выбрана «малинка» (Raspberry PI 3B). Arduino-подобные платы для такой комплексной задачи сразу отпали, так как надо контролировать минимум 4 двигателя, диоды, гироскоп, отправлять поток видео с камеры и при этом получать и обрабатывать команды с устройств управления.

Малина идет со встроенным Wi-Fi и Ethernet под RJ-45, что несомненно позволит произвести все эти операции.

Канал связи

Да, да — витая пара

С передачей данных под водой всегда было туго. Вода служит отличным экраном, и, следовательно, про всякую беспроводку можно позабыть (любителям «а как же подводные лодки» — просьба ознакомиться с размером антенны для реализации и стоимостью технологий, сложностью и шириной канала пропускания). Так что иного выхода не оставалось. Был чеклист, который успешно пройден:

  1. Возможность передачи под водой.
  2. Скорость такой передачи.
  3. Универсальность (не надо изобретать велосипед ни на распберри, ни на передатчике).

Есть специальный кабель для воды с нулевой плавучестью и прочими прелестями, но купить 100 метров отдельно нельзя, а тратить 500+ баксов на катушку я не захотел.

Передатчик «на суше»

Мини-роутер NEXX

Было 3 основных варианта передачи.

  1. Попробовать вывести проводом Wi-Fi антенну из воды на сушу (вместо витой пары кабель одножильный), но тут много было спорных моментов.
  2. Построить мост на двух Raspberry как клиент-сервер (было бы уместно, но дороже и геморней).
  3. Взять готовый мини-роутер и соединить витой парой с малиной. Этот вариант был взят за основу, так как он самый надежный, быстрый и дешевый. Как показала практика, это неплохой вариант.

Электродвигатель

N2830/2212 1000KV
После испытания 3 разных моторов выбор пал именно на эту модель. Почему? Да все просто — он достаточно мощный, у него есть вторая ось и можно повесить 2 винта. Все двигатели нормально работают в воде до первого попадания водорослей или песка. Если брать высокооборотистые и менее мощные — вода не их стихия. Более дешевые варианты тоже не оправдали надежд. В общем цена/качество.

Плата управления (ESC)

Afro ESC (30A)
Тоже довольно все просто. Ее можно запрограммировать на переключение forward/reverse, и мощности 30 ампер должно хватить на испытуемые моторы. Плюс ко всему, их не надо было ждать месяц-другой из Китая (но, конечно же, есть одно но:).

Afro ESC USB Программатор

Программатор для наших ESC. С помощью этого девайса можно залить нужную прошивку. Собственно говоря, это и есть «но». Ждать пришлось полтора месяца.

Светодиод

CREE XHP50
Эта модель способна выжигать все живое световым потоком. Такая цель и была.

Импульсный драйвер для светодиодов

7-30В 3А
Отлично подходит для наших целей — держит два вышеуказанных светодиода, а главное — подключается к малинке и позволяет плавно регулировать яркость диодов.

Гироскоп

MPU-6050
Трехосевой гироскоп/акселерометр, который будет отправлять данные на Raspberry о местоположении в пространстве. Сильно они не пригодились, но данные были :)

Камера

RPi Camera F
Или любая другая нативная камера Raspberry PI. Главное условие — чтобы она коннектилась через шлейф, а не была USB. С видеостримингом всегда были проблемы (задержки, кодинг), точнее, это не проблемы, а «надо убить много времени, чтобы завелось».

Джойстик

iPega PG-9055 — помощник управления, если у вас руки мокрые, а они такими будут, можно включить управление данным девайсом.

Батарея

Turnigy 2200mAh 3S 30C
Снова был ряд тестов и выбор. Батареи 18650 не подошли, ибо у контроллера ток разряда был 25А, чего не хватало всей системе на пиках (да, пару батарей умерло смертью храбрых после использования). Можно было попробовать соорудить на 6 батареях и получить 50А, но это сложнее. Кабелем такую мощность постоянного(!!) тока передать нереально (помним про дистанцию 100 м). Так что взял Li-Po, которая шикарно справилась со своей задачей и поместилась в корпус.

И остальное

Программирование ESC

К сожалению, Afro ESC из коробки имеет только одно направление вращения, поэтому надо «шить». Самая засада в том, что надо ждать линкер из Китая, остальное детали. Предположим, что он у вас уже есть :)

Совет, который я встретил на просторах интернета, — купить USB хаб, чтобы в случае короткого замыкания сгорел он, а не ваш дорогой ноут. Но каждый волен выбирать свою судьбу :)

Качаем файл прошивки на диск и программу для прошивки KKMulticopter Flashtool.

Теперь важно не натупить с подключением проводов, ибо последствия могут быть печальные для какого-то устройства. Краткий ликбез по ESC по проводам:

  1. Пара красный-черный толстых кабелей с бананом «папой» на конце. Силовое питание двигателя. Я подавал 12 вольт и ампер — сколько просило :)
    1. Красный — ВХОД плюс.
    2. Черный — ВХОД минус.
  2. Толстые провода красный-черный-желтый — питание на двигатель. Пока мы их не трогаем, но в дальнейшем будем соединять на аналогичные «цвета» электродвигателя.
  3. Тонкие провода управления и питания.
    1. Желтый — это непосредственно кабель, по которому передаем сигнал управления.
    2. Красный — выход 5в (в целом я его не использовал). Их есть несколько разных типов, если захотите — читайте.
    3. Черный — минус.

Подключаем тонкие провода к Linker’у: минус к минусу (черные) и сигнал на сигнал (желтые). Тонкий красный не трогаем и не подключаем. Далее даем питание на плату (черный и красный входы). Лично я использовал этот импульсный блок, но подойдет любой с похожими характеристиками.

Открываем программу прошивки и выбираем:

Programmer: Turnigy USB Linker or Afro USB Linker (зависит о того, что у вас есть).
Port: собственно тот USB, куда включили. Благо, выбор там не велик.
Baud rate: 9600.
Controller: atmega 8-based brushless ESC (8kB flash).

И выбираем уже скачанный файл прошивки. Жмем кнопку «Run» и молимся :) Скоро прошивка успешно зальется на ESC, и все будет готово. Весь процесс можете посмотреть здесь:

Из 8-ми программируемых мною ESC сдох только один :)

Настройка Raspberry PI сервера

Теперь перейдем к настройке Raspberry PI.

  1. Качаем последний дистрибутив системы тут.
  2. Скачиваем и устанавливаем Etcher.
  3. Вставляем пустую microSD флешку в картридер (тут уже сами думайте, где их взять).
  4. Открываем Etcher и выбираем zip или img Raspbian, который скачали на первом шаге.
  5. Нажимаем ’Flash!’
  6. Ждем окончания установки и извлекаем microSD.
  7. Вставляем флешку в наш Raspberry.

Для тестов нам будет достаточно «малинки», телефона на базе Android и любого Wi-Fi роутера с доступом к интернет. Если вы любитель консольного общения с устройствами, то вам ничего объяснять и не надо, сами справитесь. Для более ленивых — подключаем монитор, клавиатуру (HDMI и USB порты работают на «ура» из коробки), питание от какого-то USB-microUSB и грузим систему. Важно помнить, что на все двигатели подаем 12V, а на Raspberry — 5V. Также важно, если выключить резко питание на малину — может побиться операционная система (связано это с памятью самого устройства). Так что лучше подумать о бесперебойности.

Подключаем к сети Wi-Fi, и в целом система готова. Я бы советовал настроить ваш роутер и дать static IP адресс для малины, ибо подключаться будем именно по IP.

Если вы уже работали с Linux, то дальнейшие действия вам будут не дики. Самым простым способом создать серверное приложение будет NodeJS. Разобраться, что да как не составляет труда, так как в Гугле еще никого не банили. Открываем терминал и поехали:

sudo apt-get update
sudo apt-get dist-upgrade
sudo apt-get install -y nodejs

И проверочно запускаем node -v, дабы точно понять, что все установилось.

Можно сразу слить весь проект с репозитория и запустить (ссылка будет в конце статьи), но разве мы здесь ради этого собрались? :) Теперь создаем папку нашего проекта mkdir ~/drone и переходим в нее cd ~/drone и инициализируем проект: npm init. Установщик спросит название и индекс файл — укажем app.js.

После инициализации ставим express: npm install express. И создаем файл app.js: touch app.js.

Для тех, кто работает с GUI (моник и клава), советую поставить Atom. В нем очень даже удобно писать js-код. Для остальных — Vim, Nano, MCEdit (и сами знаете).

Далее добавляем код в app.js и сохраняем файл:

'use strict';
const express = require('express');
const net = require('net');
const app = express();

const server = net.createServer((socket) => {
  socket.on('data', (data) => {
    var command = data.toString();
  	console.log(err);
  });
}).on('error', (err) => {
  console.log(err);
  // handle errors here
  // throw err;
});
server.timeout = 0;
// grab an arbitrary unused port.
server.listen(49655, () => {
  console.log('opened server on', server.address());
});

И запускаем из консоли node app.js в папке(!!) drone. Теперь у вас крутится сервер, который слушает подключения на 49655 порт. Запомним: наш локальный IP (ifconfig в консоли) и все подключения будем производить на IP:49655.

Сейчас давайте усложнять конструкции :)

Управление электродвигателями с Raspberry PI

Итак, начинаем самую веселую часть нашей работы — пишем управление на Raspberry PI. Идем в папку проекта ~/drone и устанавливаем PiGpio библиотеку:

sudo apt-get update
sudo apt-get install pigpio
npm install pigpio

Теперь небольшой ликбез по управлению ESC. Нам надо передать некую частоту на управляемый провод (желтый тонкий) (см. раздел «Программирование ESC»). Прошивка afro_nfet_besc30_r1 имеет диапазон 1100-1900, где значение 1500 — состояние спокойствия, 1100 макс реверс, 1900 макс форвард.

Создаем файлик engines.js и в него добавляем JS-код:

//Константа состояния спокойствия
const SERVO_ZERO = 1500;
//Подключаем библиотеку 'pigpio'
const Gpio = require('pigpio').Gpio;

//Определяем наши 4 двигателя по пинам расбперри (пиновка будет объяснена чуть ниже)
const servo1 = new Gpio(22, {mode: Gpio.OUTPUT});
const servo2 = new Gpio(10, {mode: Gpio.OUTPUT});

const servo3 = new Gpio(9, {mode: Gpio.OUTPUT});
const servo4 = new Gpio(27, {mode: Gpio.OUTPUT});

//Устанавливаем каждой плате состояние “спокойствия”
servo1.servoWrite(SERVO_ZERO);
servo2.servoWrite(SERVO_ZERO);
servo3.servoWrite(SERVO_ZERO);
servo4.servoWrite(SERVO_ZERO);

//По вызову этого метода пишем значение в наш ESC
module.exports.engines = {
  leftEsc: (value) => {
    servo1.servoWrite(value);
  },
  rightEsc: (value) => {
    servo2.servoWrite(value);
  },
  topLeftEsc: (value) => {
    servo3.servoWrite(value);
  },
  topRightEsc: (value) => {
    servo4.servoWrite(value);
  }
}

Дабы использовать этот код, пишем в app.js:

const engines = require('./engines.js').engines;
engines.leftEsc(*VALUE[1100;1900]*);

Собственно говоря, servo1.servoWrite() — и есть магическое управление. Подаем 1100 — двигатель крутится в реверсном направлении. Подаем 1900 — max forward. А вот распиновка нашей малины:

Если вкратце, то мы используем оранжевые GPIO. Одна пара GPIO 10 — GPIO 22, вторая GPIO 9 и GPIO 27 (логичнее было бы использовать 27;22 10;9, но при пайке не учел, и пришлось поменять). Если брать по порядочным числам (указаны серым), то это 13;15 и 19;21 контакты. На каждый из них подключаем желтые провода из ESC, а минус же объединяем в один GND, к примеру 39 контакт.

Осталось подключить остальное. Коннектим провода питания (красный и черный) Afro ESC к отдельному блоку питания(12v), подключаем моторы к желтый-красный-черный толстым проводам и уже можем играться. Например, допилить код и по интервалу подавать разные сигналы на пины. Небольшое видео, правда, там управление осуществляется с Android, но это опишу немного позже:

Гироскоп

Подключаем:
VCC к пину 1 (3.3V PWR)
GND к любому минусу
SCL к пину 5 (GPIO 3)
SDA к пину 3 (GPIO 2)

Подключение не сложное, а программно еще проще — хорошие люди сделали все до нас. На помощь спешат библиотеки i2c-bus и i2c-mpu6050.

Устанавливаем в проект:

npm install i2c-bus
npm install i2c-mpu6050

Создаем файл gyroscope.js и добавляем:

const i2c = require('i2c-bus');
const MPU6050 = require('i2c-mpu6050');

const address = 0x68;
const i2c1 = i2c.openSync(1);
const sensor = new MPU6050(i2c1, address);

var reading = false;
var sensorData = {};

module.exports.gyroscope = {
  getData : () => {
    return JSON.stringify(sensorData);
  },
  readRotation : () => {
    sensor.readRotation(function (err, rot) {
      if (err) {
        console.log(e);
        return;
      }
      console.log(rot);
    });
  },
  readSensor : () => {
  	if (!reading) {
  		reading = true;
  	}
  	sensor.read(function (err, data) {
      reading = false;
  		if (err) {
  			console.log(err);
  		} else {
        sensorData = data;
  		}
  	});
  }
}

Запускаем метод readSensor с какой-то периодичностью и забираем данные последнего опроса из getData. Там будет много составляющих (x,y,z,a) о положении. Разобраться что к чему, честно, не успел. Были важнее задачи с управлением. Знаю, решение такое себе, но оно имело чисто ознакомительный характер, поэтому и актуальность результата не очень важна.

Raspberry PI Flashlight control

Потихоньку продолжаем наращивать функционал и на этот раз займемся светодиодами. У нас есть два XHP-50 диода и драйвер-контроля (см. раздел «Выбор компонентов»). Они очень хорошо греются, поэтому без радиаторов использовать их никому не рекомендую. На том же сайте, благо, они есть. Главное, не напутать с размером.

Итак, прикручиваем диоды на радиатор, паяем минус одного к плюсу другого (на них есть маркировка) и припаиваем минусовой и плюсовой провода:

Далее смотрим на наш контроллер и тоже паяем.

Питание — 12V плюс и минус, можно запитаться тем же, чем и питаем двигатели. Выводы на диод соединяем с ранее припаянными соответственно L- к минусу, L+ к плюсу. Аналоговый контакт (А) в данной реализации не нужен, оставляем пустым.

Теперь нас интересует цифровой вход (D) и земля (G). Коннектим цифру (D) на GPIO 11 и землю на многострадальный GND (любой, можно один). Теперь возвращаемся к нашей Raspberry PI, создаем файлик light.js и добавляем следующее:

//Наша знакомая библиотека
const Gpio = require('pigpio').Gpio;
//Создаем Gpio для нашего пина
const light = new Gpio(11, {mode: Gpio.OUTPUT});
// Значение можно ставить от 0 до 255 (из доков по драйверу светодиода)
const LIGHT_MIN = 0;
const LIGHT_MAX = 255;
//Нужно будет для вычислений
const LIGHT_DIAPASONE = LIGHT_MAX - LIGHT_MIN;
//Выключаем светодиод изначально (система стартует с включенными на максимум светодиодами)
light.pwmWrite(LIGHT_MIN);
//Экспортируем методы работы
module.exports.light = {
  on: () => {
    //Передаем на драйвер значение 255
light.pwmWrite(LIGHT_MAX);
  },
  off: () => {
//Передаем на драйвер значение 0
    light.pwmWrite(LIGHT_MIN);
  },
//Здесь принимаем значение в процентах от 0 до 100
  set: (val) => {
    if(val < 0 || val > 100) return;
//Вычисляем значение исходя из процента
    val = Math.round(LIGHT_MIN + LIGHT_DIAPASONE / 100 * val);
    console.log("Light:"+val);
//Передаем на драйвер значение
    light.pwmWrite(val);
  }
}

Запустить можем из нашего app.js так же, как и engines:

const light = require('./light.js').light;
light.on();
light.off();
light.set(50);

Видеодемонстрация готового решения:

Если понять главную идею, то все предельно просто: подключил, подал сигнал, смотришь результат. Грубо говоря, это весь функционал контроля, который нам нужен для активизации элементов. Но все это бесполезно без какого-то контроллера, который и будет включать/регулировать все это, а это говорит о том, что надо построить коммуникацию между сервером и клиентом. Значит придумаем протокол :)

Протокол общения

Как вы поняли из первоначальной настройки Raspberry PI, общение будет произведено по сокет подключению. И нужен какой-то набор команд, который будет захардкожен на сервере и клиенте. Так как я сам себе д’артаньян, то решил использовать свой формат, ибо варианты, которые есть, не совсем подходят по критерию вес/читаемость. Строим, основываясь на базе модели управления, а это телефон с Android на борту.

Вот приблизительно и сама модель (программа будет описана чуть ниже). Экран разделен на две части. Левая отвечает за горизонтальную плоскость (вперед и назад), правая — за вертикальную.
Ставите палец в какой-то области — это считается точкой отсчета и контроля парой двигателей, но при этом это точка спокойствия.

Если ведете его вертикально вверх — потихоньку повышается скорость вращения обоих двигателей в прямом направлении с мощностью от 0 до максимума (зависит от расстояния от точки касания до текущей).

Если двигаем вниз: оба двигателя вращаются в реверсном направлении, со скоростью, зависимой от удаления от точки касания.

Если вправо: левый крутится в прямом, правый в реверсном.

То есть по факту нам нужно знать угол отклонения от точки касания (где верх = 90, низ — 90, право 0, лево 180) и коэффициент скорости — удаленность пальца от точки касания (от 0 до 100). Конечные команды выглядит так:

C:L;A:45;V:35;
C:R;A:0;V:100;
C:LIGHT;V:50;

С — любая команда начинается с этой буквы (command);

: — разделитель ключ-значения;

; — разделитель пар ключ-значение;

L — говорит, что нажатие было в левой части экрана;

R — соответственно в правой;

LIGHT — внезапно, свет :)

A — ANGLE угол отклонения;

V — value значение чего-то.

То есть первая команда говорит: «Горизонтальна пара двигателей, направление 45 градусов (левый на максимуме (но максимум — это 35%, исходя из V), правый стоит), со скоростью вращения 35% от максимума».

Вторая соответственно: «Вертикальная пара двигателей, направление 0 градусов (левый на максимуме, правый на максимальном реверсе), с максимальной скоростью вращения». Третья: «Включить свет на 50% яркости».

Так и шлем по сокету на каждое изменение какого-то значения отдельные команды блоками ключ-значение. Далее магическим способом превращаем это в команды на платы управления. Расписывать преобразование этих данных — сильно скучно, здесь и так много текста. Так что каждый может включить воображение или попытаться разобраться в контроллере.

Приложение на Android

Наконец-то мы «доехали» до создания контроллера для всего этого. В целом, общение состоит из «нажал на что-то на экране — передал значение на сервер». Вкратце пройдемся по базовому приложению. Здесь нет ничего необычного и сверхсложного, поэтому весь код выкладывать не буду. При желании зайдете на репозиторий и посмотрите. Также хочу отметить, что все новомодные паттерны MVVM, MVP, VIPER и т. д. пошли лесом, ибо мне надо было рабочее приложение в минимальные сроки, а не выпендриваться. Юайка простая, так что делается максимально просто.

Первым делом создадим активность с вьюхами отображения состояния.

Лаяут будет следующий:

  1. Слой отображения видео (плеер инкапсулирован во фрагмент).
  2. Слой JoystickView — кастомная вьюха, в которой переопределен метод dispatchTouchEvent и в нем обрабатываем нажатие и перемещение пальцев по экрану. Она же рисует UI-линии от нажатия до текущего состояния. Обязательный аспект — поддержка мультитача, так как управление осуществляется двумя пальцами.
  3. Слой с TextView текущей температуры Raspberry PI (если что-то пошло не так и она начинает греться — можно прекратить использование раньше, чем она выключится/сгорит). Благо, такого не было.
  4. Слой отображения данных с гироскопа. Набор TextView в парах label-значение. Выводит «сырые данные» с гироскопа. Планировалось их обрабатывать, но времени не хватило для разбора всего и изобретения крутого отображения.
  5. Слой с SeekBar — управление светодиодом, простой элемент, при перетягивании которого меняется значение от 0 до 100.

Видео Stream

Тут было проделано достаточно много работы для подбора необходимых способов стриминга.

HLS сразу отпал, так как задержка была порядка 10 секунд. При желании можно было добиться 5, но это все равно далеко не то, что надо. В управлении крайне необходимо передать видео с минимальной задержкой. Но передачу организовать не сложно, что со стороны Raspberry, что отображение с Android.

RTMP уже будет потяжелее. Проблема в том, что не было особо простого варианта реализации на Android (на момент конца 2017 года). Есть платные варианты, с которыми тоже повозиться, есть многострадальный VLC, который надо компилить с C++ и настраивать, есть уже собранный VLC как библиотека, который тоже из коробки не хотел проигрывать. Если прибавить ко всему, что это было как хобби и делалось в вечернее послерабочее время, когда голова не сильно свежая и времени ±2 часа, то эти варианты я отбросил.

Зато нашел очень даже рабочее решение, которое подходило по моим параметрам. RPi Camera Viewer for Android — решение, которое получает Raw H.264 поток с камеры и отображает на TextureView c использованием нативного MediaCodec. Если честно, то я до конца не раскрутил всю логику отображения, но собрал и чуть модифицировал под себя для использования. Собственно, главным со стороны Android выступает DecoderThread. Он коннектится по IP и порту, декодит поток и отдает в Surface, а в случае обрыва или ошибки идет рекконект каждые 2 секунды.

Со стороны Raspberry команда запуска потока будет следующая:

raspivid -t 0 -w 1280 -h 720 -hf -ih -fps 10 -o - | nc -k -l 2222

Raspivid — команда, которая осуществляет «захват» видео с подключенной камеры (но не USB, а именно подключенной шлейфом). Параметры выбирал методом «научного» тыка. Качество 1280×720, частота кадров 10fps. Безумно мало, но минимальная задержка.

И весь поток, который транслируется с камеры передается без изменений командой Netcat на 2222 порт.

Приложение на Android коннектится к нему и уже обрабатывает видео, отображая на экране.
Неделю вечеров пришлось потратить на поиск и тестирование таких простых решений, так как общение с Raspberry для меня было в новизну, как и стриминг с Unix-системы.

Управление с экрана и джойстиком

Как уже было сказано ранее, JoystickView передает угол и значение касания экрана на сокет в виде команды типа C:L;A:45;V:35;

Есть поток подключения к сокету (с реконнектами, колбеками и прочим), в котором находится очередь команд. Циклом выбирает их и отправляет на сервер.

В помощь экранному управлению допилил управление джойстиком.

Данный девайс коннектится по Bluetooth и работает как клавиатура. Следовательно, все данные обрабатываются MainActivity методами onKeyDown и onGenericMotionEvent.

Тест управления:

Вроде ничего важного со стороны Android не забыл, если что-то не раскрыл — пишите, объясню, допишу.

Сборка и тестирование конструкций

Теперь мы добрались до следующего шага — засунуть все как-то во что-то (гы-гы). На этапе проектирования и сборки мне помогал один очень хороший человек по имени Сергей. Собственно, он нашел коробку, сделал некую железную платформу, на которую можно было крепить двигатели в разных позициях, и собрал все это вот в такой вариант:

Забегу вперед и скажу, что вариант с 6-ю двигателями был бы продуктивнее, но я не выгребал проектирование четырех, и было решено тестить урезанную версию. Изначально мы решили протестить конструкцию в целом и не стали засовывать электронику в коробку, а просто прикрепили двигатели к платформе, да по двум витым парам подали на каждый из них сигнал из ESC (который оставался всегда на суше).

Вот такой адок получился в итоге:

Был забит болт на потери при передаче (кабеля было 10 метров) и на основные косяки такого решения. Потому основная цель была посмотреть:

  1. Можно ли жить с 4-мя движками?
  2. Можно ли управлять с телефона?
  3. Будет ли затекать вода в коробку?

Но я упустил один важный момент, о котором Серега меня предупреждал. После теста в ванной все эти контакты надо было хорошенько просушить, ибо они — самое слабое звено. А я забил и получил в итоге комбо из 48 мест(!!!) соединения, которые могут (а если могут — обязательно будут) терять контакт.

Здесь как на всех мемасиках ожидание-реальность.

Тест в ванной:

Его сильно кренит из-за проводов, но он способен перемещаться. Ну и чутка перегружен грузами, так как коробка пустая — не рассчитал. А вот следующий тест в «поле» оказался еще хуже:

Двигатели и контакты чуть поддались коррозии, из-за низкой температуры кабель потерял пластичность, и уже все происходило не так резво. Да и гребные винты были неподходящего размера.

Но, самое главное, что был уже понятен путь доработок и конструкционных изменений. Изначально я пытался как-то оставить питание вне аппарата, но тесты показали, что это просто невозможно. Огромные потери постоянного тока говорили о том, что или надо использовать кабель толщиной с руку, или засунуть батареи во внутрь.

Так как никто не был уверен, что на глубине 10+ метров эта конструкция не даст течь и не зальет электронику — решили сделать усиленный вариант «коробка в коробке». Заказали в Китае все необходимое и приступили к дальнейшей сборке:

В коробке были просверлены отверстия для проводов, которые выходили через cable glands (забыл, как они называются по-другому).

Дополнительно была прикручена коробка для диодов и камеры. Естественно, на резиновую прокладку и плотненько зажата винтами. Далее была спаяна плата соединений, с отдельными разъемами под каждый ESC, драйвер диода и гироскоп.

Дабы упростить из без того нагроможденные конструкции, питание сделали одно (для всех узлов) — от LiPO батареи, так как только она способна выдать такой ток разрядки, который покрыл бы и Raspberry, и диоды, и двигатели. Но дополнительно добавили плату уменьшения напряжения с 12V до 5V, которая питала только малину. «Работаем дальше» ©

Получили вот такой внутренний дата-бокс. 4 вывода мотора, 2 диода и шлейф видео. С ним пришлось повозиться больше всего. Правда, в самый последний момент подключения камера не взлетела (на тестах было все хорошо) — где-то таки переломал шлейф во время бесконечных разборок и сборок. Так как уже не было сил это все пересобирать еще раз — решено было забить, да прикрепить на скотч экшн-камеру в водозащитном кейсе и провести новые тестирования.

Кстати, были заменены также гребные винты на максимально большие из тех, которые продаются для моделей, и заменены двигатели на более мощные. На каждый электромотор, который отвечал за горизонтальную плоскость, было прикреплено по 2 винта, просто потому, что они были :)

В качестве роутера был подключен наш NEXX. То есть из Raspberry PI выходит витая пара и коннектится посредством RJ-45 в маршрутизатор. Он же, в свою очередь, раздает Wi-Fi для Android-телефона. На малине забит статический IP адрес, что позволяет не настраивать каждый раз подключение. Правда опять есть проблемы с подключением — если нет нормального покрытия связи, телефон не может присоединиться нормально по IP. Для этого отключал мобильный интернет и пытался присоединиться несколько раз. После подключения открывал SSH на телефоне и запускал команды запуска node сервера (а после всего — выключения системы).

Пришло время новых тестов и, казалось бы, что может пойти не так? Да все :)

По приезде на киевское водохранилище были обнаружены волны нечеловеческих размеров (ну это я так себя утешаю).

И последствия таких заплывов: полностью забитые двигателя всякой плавающей штукой типа водорослей, песком. Тест был снова провален.

Третий и самый успешный тест был произведен уже на спокойном озере, что давало немалые преимущества по сравнению с первыми двумя.

Выводы

Главное, что из этого всего получилось — большой опыт в построении таких вот связок и систем. В процессе разработки было использовано немалое количество различных девайсов, и их управление оказалось намного проще, чем это выглядит издалека.

Конструкция на четырех двигателях не сильно оправдала свои ожидания — управлять таким девайсом крайне тяжело. В идеале надо делать четыре вертикальных двигателя, минимум два горизонтальных. При этом стабилизировать все гироскопом, ибо человек теряется в управлении, и ему надо помогать (как это делают все летающие собратья).

Также все моторы, которые есть в продаже для воздуха очень быстро забиваются песком и прочим, поддаются коррозии отдельных элементов (что логично) и вряд ли подойдут под такие проекты. Конкретно моторов для подводной работы я не нашел (точнее на них может и написано, что waterproof, но это от брызг и глубины до метра).

После этого проекта любой умный дом не кажется таким уж сложным. Сервер, команды на реле, да и все. Главное, время и желание. Остальное придет.

А смысл?

А его нет.

Обещанные ссылки:

Для запуска используем файл app.js.

Всем добра.

Похожие статьи:
Длительность: 15 недельРасписание: вт, пт 18.30 — 19.00. В Kit Center стартует курс QA automation (Java + Selenium). Как всегда, используем методологию...
DOU запросив досвідчених експертів з Agile — Артема Биковця, CEO Simplesense, Ярослава Бондарчука, Delivery Management Consultant в Intellias, та Ярослава...
От редакции:В рубрике DOU Проектор все желающие могут презентовать свой продукт (как стартап, так и ламповый pet-проект). Если...
AI HOUSE спільно з Факультетом прикладних наук Українського католицького університету організовують школу AI Product: from Scratch...
Как правило, маленькие компании, которые только выходят на рынок и собираются громко заявить о себе, сталкиваются...
Яндекс.Метрика