DIY: Робот-подводная лодка для исследования акватории Москвы-реки. Часть 1
Это краткая история создания подводного автономного робота менеджером очень среднего звена. Текущей целью является создание актуальной карты фарватера Москвы-реки. При поиске нет проблем найти такую карту, но вызывает вопрос ее актуальность. Русло реки постоянно меняется. Происходит эрозия берегов реки и меняется карта фарватера. Эти процессы особенно заметны, учитывая питание Москвы-реки снеговое (61%), грунтовое (27%) и дождевое (12%). Конечной целью является создание многофункционального подводного робота для исследование морских глубин. Мировой океан, покрывающий 2/3 поверхности Земли, изучен всего на 5%. Для создания автономного робота нужен простой «автопилот».
Как сделать доступный и простой автопилот для DIY-проектов и проплыть на нем по Москве-реке?
Москва-река это главная водная артерия города Москвы, длина в пределах города 80 км. Ширина реки внутри города меняется от 120 до 200 м, от самой узкой части возле Кремля до самой широкой вблизи Лужников. Принято считать, что скорость течения реки 0,5 м/с. Вполне благоприятные условия для испытания робота.
Возможно, было бы эффективнее и проще сделать лодку, но задача сделать подводного робота кажется много интереснее.
Конструкция
Робот-подводная лодка для исследования акватории Москвы-реки.
Примерное расположение элементов я видел таким.
Установка коллекторного двигателя
Серво-привод управления рулем
Серво-привод управления рулем глубины
В шутку можно сказать что я готовлюсь к конкурсу X-Prize.
The registration deadline is 30 June, 2016 (11:59 PM UTC/4:59 PM PST). The registration fee is $2,000.00 USD.
Целью является разработка автономного подводного робота, а не ROV’а. (Remotely operated underwater vehicle).
Электронная часть не отличается сложностью и качеством сборки
Arduino nano + L293D + Bluetooth + 3 серво + Power bank (Отдельное спасибо интернет-магазину, который продавал такие по 3 цента). Отдельно подключается к Power bank смартфон Samsung Galaxy S3 для поддержания заряда. Связь смартфона с Arduino осуществляется по Bluetooth.
Программная часть управления
«Мы встречаем свою судьбу на пути, который избрали, чтобы уйти от нее»
Жан де Лафонтен
Я всегда старался избегать программирования на Android. Когда появлялась возможность разработки даже легкого приложения с возможностью в действии познакомится с Android, я пасовал.
Но час настал! Действительно, хватит тратить деньги на шильды для Arduino. Все что нужно уже есть в отслуживших смартфонах. Под рукой оказался Samsung Galaxy S3 и немного магии.
Как порядочный ГИК я задал вопрос на тостере. Возможно я спрашивал сложно. Возможно плохо сформулировал вопрос. И похоже это было скорее не на вопрос. Но в ответ я получил совсем не то, что ожидал.
Яндекс мне в бухту! Все запросы «Android GPS», «Программирование Android GPS» и прочее давали ответы, которые решительно не работали на Android Studio.
Каким же облегчением для меня стала находка SL4A. Оказывается можно прототипировать и программировать на Android, используя Python. Управляют, ведь, даже ракетами.
SL4A Python
Если у Вас есть телефон Android, а он у Вас есть. Вы же ГИК, а не любитель приторных фруктов.
Устанавливаем на выбор программу для распознавания QR-кодов.
Для использования SL4A необходимо установить на телефон приложение.
Интерпретатор языка Python находится здесь.
Или QPython3
Писать программы на телефоне возможно, но занятием увлекательным это не назовешь.
Перенаправим весь локальный трафик, поступающий на порт 9999 в Android-устройство (примем, что сервер слушает порт 46136):
$ adb forward tcp:9999 tcp:46136
Осталось создать переменную окружения и настройка закончена:
$ AP_PORT=9999
Осталось добавить файл android.py в папку с библиотеками Python’а, и все, теперь можно писать приложения на компьютере, запускать их, и результат можно буждет видеть сразу на телефоне. Для запуска helloWorld на Android-устройстве теперь достаточно ввести в интерпретаторе Python:
>>>import android
>>>droid = android.Android ()
>>>droid.makeToast («Hello, world!»)
В первой строчке импортируется библиотека android, затем создается объект droid, с помощью которого используются API Android’a. Крайняя строка выводит сообщение «Hello, World!» на экран устройства.
Теперь самое время, чтобы познакомиться поближе с API, которое предоставляет SL4A.
Построение пути. Выбор точек
Используя Яндекс.карты или Google.maps выбираем точки по центру реки. Это примерный маршрут. Для тестов и наладки использую усеченную версию точек.
[55.671110, 37.686625],[55.668448, 37.675467],[55.660847, 37.671776],[55.654649, 37.671175]
Как сделать доступный и простой автопилот для DIY-проектов из andoid телефона и 70 строк кода?
import math,android,time
coordmas = [[55.671110, 37.686625],[55.668448, 37.675467],[55.660847, 37.671776],[55.654649, 37.671175]]
droid = android.Android()
droid.startSensingTimed(1,200)
droid.startLocating(5000, 30)
def getgps():
locs = droid.getLastKnownLocation()
gpspos = locs.result["gps"]
return gpspos
def distazim(llat1,llong1,llat2,llong2):
rad=6372795
#врадианах
lat1=llat1*math.pi/180.
lat2=llat2*math.pi/180.
long1=llong1*math.pi/180.
long2=llong2*math.pi/180.
cl1=math.cos(lat1)
cl2=math.cos(lat2)
sl1=math.sin(lat1)
sl2=math.sin(lat2)
delta=long2-long1
cdelta=math.cos(delta)
sdelta=math.sin(delta)
y=math.sqrt(math.pow(cl2*sdelta,2)+math.pow(cl1*sl2-sl1*cl2*cdelta,2))
x=sl1*sl2+cl1*cl2*cdelta
ad=math.atan2(y,x)
dist=ad*rad
x=(cl1*sl2)-(sl1*cl2*cdelta)
y=sdelta*cl2
z=math.degrees(math.atan(-y/x))
if(x<0):
z=z+180.
z2=(z+180.)%360.-180.
z2=-math.radians(z2)
anglerad2=z2-((2*math.pi)*math.floor((z2/(2*math.pi))))
angledeg=(anglerad2*180.)/math.pi
return [dist,angledeg]
def servoangle(azdiff):
if azdiff>10 or azdiff<-10:
deg=azdiff
if deg>90:
deg=90
if deg<-90:
deg=-90
return deg
gpspos=getgps()
oldlat = gpspos["latitude"]
oldlon = gpspos["longitude"]
for c in range(len(coordmas)):
#получение координат текцщей целевой точки
curcoord=coordmas[c]
targetlat=curcoord[0]
targetlon=curcoord[1]
darange=11;
while darange>10:
gpspos=getgps()
curlat = gpspos["latitude"]
curlon = gpspos["longitude"]
time.sleep(0.5)
da=distazim(curlat,curlon,targetlat,targetlon)
darange = da[0]
dazimut = round(da[1])
pol=droid.sensorsReadOrientation()
pol2=pol.result
turn = round(pol2[0])
turn = (-turn) *180/ 3.2
azdiff=turn-dazimut
deg=servoangle(azdiff)
oldlat = curlat
oldlon = curlon
import math,android,time
#Массив координат
coordmas = [[55.671110, 37.686625],[55.668448, 37.675467],[55.660847, 37.671776],[55.654649, 37.671175]]
#Инициализация, запуск GPS, сенсоров
droid = android.Android()
droid.startSensingTimed(1,200)
droid.startLocating(5000, 30)
#Функции
#GPS
def getgps():
locs = droid.getLastKnownLocation()
gpspos = locs.result["gps"]
return gpspos
#Функция определения расстояния и азимута между двумя GPS-точками
def distazim(llat1,llong1,llat2,llong2):
rad=6372795
#врадианах
lat1=llat1*math.pi/180.
lat2=llat2*math.pi/180.
long1=llong1*math.pi/180.
long2=llong2*math.pi/180.
#косинусыисинусыширотиразницыдолгот
cl1=math.cos(lat1)
cl2=math.cos(lat2)
sl1=math.sin(lat1)
sl2=math.sin(lat2)
delta=long2-long1
cdelta=math.cos(delta)
sdelta=math.sin(delta)
#вычислениядлиныбольшогокруга
y=math.sqrt(math.pow(cl2*sdelta,2)+math.pow(cl1*sl2-sl1*cl2*cdelta,2))
x=sl1*sl2+cl1*cl2*cdelta
ad=math.atan2(y,x)
dist=ad*rad
#вычислениеначальногоазимута
x=(cl1*sl2)-(sl1*cl2*cdelta)
y=sdelta*cl2
z=math.degrees(math.atan(-y/x))
if(x<0):
z=z+180.
z2=(z+180.)%360.-180.
z2=-math.radians(z2)
anglerad2=z2-((2*math.pi)*math.floor((z2/(2*math.pi))))
angledeg=(anglerad2*180.)/math.pi
return [dist,angledeg]
def servoangle(azdiff):
if azdiff>10 or azdiff<-10:
deg=azdiff
if deg>90:
deg=90
if deg<-90:
deg=-90
return deg
#принятие текущего положения за старую точку
gpspos=getgps()
oldlat = gpspos["latitude"]
oldlon = gpspos["longitude"]
#Цикл
for c in range(len(coordmas)):
#получение координат текцщей целевой точки
curcoord=coordmas[c]
targetlat=curcoord[0]
targetlon=curcoord[1]
#выполняем цикл пока расстояние больше 10 метров до след.точки
darange=11;
while darange>10:
#определение координат
gpspos=getgps()
curlat = gpspos["latitude"]
curlon = gpspos["longitude"]
time.sleep(0.5)
#сравнение дальности и азимута до след. точки.
da=distazim(curlat,curlon,targetlat,targetlon)
darange = da[0]
dazimut = round(da[1])
#определяем угол телефона
pol=droid.sensorsReadOrientation()
pol2=pol.result
turn = round(pol2[0])
#[1.57=left, 0=stop, -1.57=right]
turn = (-turn) *180/ 3.2
#если угол точки отличается от угла телефона на 10 градусов готовим команду на поворот
azdiff=turn-dazimut
#готовим данные для угла поворота сервы руля
deg=servoangle(azdiff)
#отправляем по блютус угол поворота серво
#Меняем координаты старой точки, т.к. ожидается смена координат текущей цели
oldlat = curlat
oldlon = curlon
droid.batteryStartMonitoring () — началo работы с батареей.
droid.batteryStopMonitoring ()
droid.batteryGetHealth () — возвращает состояние батареи (1-неизвестно, 2-хорошее, 3 — перегрев, 4 — мёртвая, 5 — перегрузка, 6 — неизвестный сбой)
droid.batteryGetStatus () — возвращает статус батареи (1 — неизвестно, 2 — заряжается, 3 — разряжается, 4 — не заряжается, 5 — максимальный заряд)
droid.batteryGetTechnology ()
droid.readBatteryData () — данные о батарее.
droid.batteryGetTemperature ()
droid.batteryGetVoltage ()
droid.batteryGetLevel ()
Bluetooth:
droid.checkBluetoothState () — проверяет включён ли Bluetooth
droid.toggleBluetoothState () — включает если в скобках True и выключает, если False
droid.bluetoothAccept () — принимает соединение
droid.bluetoothActiveConnections () — проверяет, есть ли подключения
droid.bluetoothGetConnectedDeviceName ()
droid.bluetoothMakeDiscoverable () — в скобках можно указать промежуток времени в секундах
droid.bluetoothStop ()
Wi-Fi:
droid.checkWifiState () — проверяет включён ли Wi-Fi
droid.toggleWifiState () — включает если в скобках True и выключает, если False
droid.wifiStartScan ()
droid.wifiGetScanResults ()
droid.wifiGetConnectionInfo ()
Другие настройки:
droid.checkAirplaneMode () — проверяет включён ли режим «В самолёте»
droid.checkRingerSilentMode () — проверяет включён ли беззвучный режим
droid.checkScreenOn () — включён ли экран
droid.toggleRingerSilentMode () — включает беззвучный режим
droid.toggleAirplaneMode ()
droid.toggleVibrateMode ()
Получение информации о настройках:
droid.getMaxMediaVolume ()
droid.getMaxRingerVolume ()
droid.getMediaVolume ()
droid.getRingerVolume ()
droid.getScreenBrightness ()
droid.getScreenTimeout ()
droid.getVibrateMode ()
Установка параметров:
droid.setMediaVolume ()
droid.setRingerVolume ()
droid.setScreenBrightness ()
droid.setScreenTimeOut ()
В планах:
- Настройка отправки данных по Bluetooth на arduino.
- Получение с arduino данных о глубине. Глубину буду измерять ультразвуковым датчиком.
- Отправка данных о текущем положении и измерений глубины на сервер.
- Доработка конструкции. Лодка должна быть с нулевой плавучестью, чтобы глубину можно было изменять рулями глубины. Возможно от конструкции подводной лодки придется уйти к надводному аппарату.
- Тест, тест, тест
P.S.: Почему я решил написать пост до запуска робота? Я хочу найти единомышленников. Если у Вас есть желание — делайте своего робота. Отличный повод собраться на майских праздниках на аквапробег по Москве-реке на роботах! По всем вопросам можете писать мне ВК