У Вас есть минутка? Не могли бы Вы покосить мой газон?
Краткая история разработки на коленке робота-газонокосилки. Управлять ей можно с любой точки земли через интернет. Мечтали почувствовать себя оператором марсохода или лунохода? Всего лишь нужно зайти на сайт mowmylawn.ru и Вы сможете управлять газонокосилкой у меня во дворе!
Предыстория
Вся история с газонокосилками началась летом 2015, мой первый пост на GT как раз был о моем опыте реализации робокосилки из того, что было в гараже.
После этого представители компании Robomow предложили использовать их платформу для дальнейшей разработки. Это очень удобно, потому что все вопросы по железу и механике требуют особых навыков и занимают очень много времени.
Я очень хотел организовать конкурс роботов-газонокосилок. Как оказалось дело это нелегкое. Команд было зарегистрировано более 15, но в итоге только трое участников показали ход работ.
Электроника: Две платы. Raspberrry Pi — в качестве управляющей системы верхнего уровня. В ее задачи входит планирование маршрута, построение траекторий, и тд. Nucleo stm32 — в качестве исполнительной системы нижнего уровня. В ее задачи входит навигация, опрос датчиков, управление моторами итд.
Навигация: Система комплексирования данных RTK, одометров и ИНС. Это система собственной разработки.
Датчики: Навигационные приемники GPS/GLONASS. Энкодеры моторов для одометрии. Инерциальная система навигации. Ультразвуковые датчики.
К сожалению должен сообщить что наша команда, скорее всего, не сможет принять участие в конкурсе. Дело в том что во время первых полевых испытаний в конструкции нашего робота были выявлены определенные недочеты, на исправление которых уйдет некоторое время. Работу над проектом мы приостанавливать не собираемся, но на фоне грядущей сессии, представить даже минимальную версию робота к намеченному сроку — вряд ли сможем(что уж говорить про использование машинного обучения, которое должно было стать нашем сильным преимуществом).
Все усилия по проведению конкурса в Сколково оказались зря. В конце концов оказалось, что свободных газонов для конкурса по робототехнике в Сколково нет. Все возможные спонсоры не хотят связываться с новым конкурсом с неясными перспективами. На письма в муниципальные организации я даже не получил ответ. В итоге у нас была туманная перспектива организовать конкурс для 4 участников из разных городов. Хотя конкурс получался международный, все-таки целесообразней было его отменить, как это не жаль.
Робокосилка 2016. ROS & FUN
Я осваиваю ROS и пишу лаунчер для робокосилки, использую Kinect и SLAM, только на визуальной одометрии робот строит карту и прокладывает маршрут. Kinect в солнечную погоду работает плохо. С ROS Вы за один вечер сделаете «Hello World!», а дальше тьма. Я так и не нашел нормального руководства для новичка как сделать робота, а не просто писать в топики. Кто-нибудь задумывался, почему на карте сообщества ROS нет ВООБЩЕ ни одной точки в РФ? Я открыл группу ВК.
В свободное время прикрутил к тягам своей «рабочей лошадки» два сервопривода, ультразвуковой сенсор и Arduino. Простой тест объезда березы и остановки пройден! А дальше бездна, эту штуку опасно просто так пускать по своему участку! Ее даже не получится пинать как роботов сами знаете откуда.
Оказывается роботы — вещи достаточно скучные в понимании большинства людей, особенно сервисные роботы. Да, знаю о великой дружбе роботов-пылесосов и котиков. Знаю о супер творениях от Boston Dynamics, Darpa и российском боевом роботе-аватаре, это все больше похоже на роботов, чем коробочка, в которой некий алгорим взаимодействует с реальным миром.
В понимании моей дочки роботы — это как минимум трансформеры, а не та еруда, на которую я трачу время. Я принял тяжелое решение и «временно» сделал из робокосилки игрушку на bluetooth управлении.
Управление работает так же как и у снегоуборщика. Оказалось это весело! Особенно для папы.
Дальше — больше! Меня заинтересовала идея реализации управления не на bluetooth, а через интернет, с телеметрией. Скучно, пресно и идея избитая!
А что если дать возможность любому пользователю интернета управлять моей газонокосилкой? Знаете такие идеи, которые потом трудно выкинуть из головы? Это как раз была такой...Сhallenge accepted!
Краудкосилка
Железо
В волшебную коробочку аккуратно добавлены Raspberry pi, USB хаб, wifi-адаптер, веб-камера.
Из интересных моментов по железу. У меня не оказалось драйвера с нужными характеристиками для двигателей. Обычно в качестве драйвера используют H-мост на полевых транзисторов или (хардкор) на реле. Я выбираю более жесткий вариант, потому что именно реле были в наличии.
Обычная схема подключения подразумевает 4 ключа на каждый двигатель, т.е. 8 на 2 ходовых двигателя.
Учитывая, что можно использовать так же и нормально-открытое состояние реле, а так же тот факт, что нет необходимости приводить двигатели в движение по отдельности можно обойтись всего лишь 5 реле для двух ходовых двигателей.
Кроме самого робота установлена на доме камера, которая с частотой 3 кадра/сек. загружает на ftp-сервер в интернете обзорное фото участка для лучшей ориентации. Но впоследствии просто заменил на решение от ivideon.
Программа
Как и прежде Arduino получает по serial-порту сообщения в один символ, которые обозначают необходимое действие. Так же для тестирования и отладки, полученные от raspberry коды отправляются по bluetooth, можно подключить телефон в режиме терминала и получать данные с сервера еще и на телефон.
int m1=2;
int m1b=3;
int m2=4;
int m2b=5;
int mk=6;
int pis=7;
char a,b;
void setup()
{
Serial.begin(9600);
Serial1.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for Leonardo only
}
while (!Serial1) {
; // wait for serial port to connect. Needed for Leonardo only
}
Serial.println("Start");
pinMode(statpin, OUTPUT);
pinMode(m1, OUTPUT);
pinMode(m1b, OUTPUT);
pinMode(m2, OUTPUT);
pinMode(m2b, OUTPUT);
pinMode(mk, OUTPUT);
pinMode(pis, OUTPUT);
analogWrite(pis, 1000);
delay(100);
analogWrite(pis, 700);
delay(200);
analogWrite(pis, 300);
delay(300);
analogWrite(pis, 1000);
delay(100);
analogWrite(pis, 100);
digitalWrite(pis, HIGH);
digitalWrite(statpin, LOW);
digitalWrite(m1, LOW);
digitalWrite(m1b, LOW);
digitalWrite(m2, LOW);
digitalWrite(m2b, LOW);
digitalWrite(mk, LOW);
digitalWrite(pis, HIGH);
}
void loop() // run over and over
{
if (Serial.available()){
a=Serial.read();
Serial1.println(a);
if(a=='B'){
digitalWrite(m1, HIGH);
digitalWrite(m1b, LOW);
digitalWrite(m2, HIGH);
digitalWrite(m2b, LOW);
}
if(a=='F'){
digitalWrite(m1, HIGH);
digitalWrite(m1b, HIGH);
digitalWrite(m2, HIGH);
digitalWrite(m2b, HIGH);
}
if(a=='R'){
digitalWrite(m1, HIGH);
digitalWrite(m1b, LOW);
digitalWrite(m2, HIGH);
digitalWrite(m2b, HIGH);
}
if(a=='L'){
digitalWrite(m1, HIGH);
digitalWrite(m1b, HIGH);
digitalWrite(m2, HIGH);
digitalWrite(m2b, LOW);
}
if(a=='S'){
digitalWrite(m1, LOW);
digitalWrite(m1b, LOW);
digitalWrite(m2, LOW);
digitalWrite(m2b, LOW);
}
if(a=='W'){
digitalWrite(mk, HIGH);
}
if(a=='w'){
digitalWrite(mk, LOW);
}
if(a=='V'){
digitalWrite(pis, LOW);
}
if(a=='v'){
digitalWrite(pis, 700);
}
}else{
}
}
На raspberry работают два python скрипта. Один из скриптов с помощью opencv захватывает видео с веб-камеры, установленной на ровере и загружает ее по ftp на сервер. Так же, с какой-то долью вероятности, фото вместе с рандомным сообщением из списка загружается в twitter-аккаунт.
import numpy as np
import sys
import pygame
import pygame.camera
from pygame.locals import *
from twython import Twython
from random import random
import ftplib
pygame.init()
pygame.camera.init()
cam = pygame.camera.Camera("/dev/video0",(550,400))
cam.start()
CONSUMER_KEY = '-------MBZDT1PwibFeIcSp'
CONSUMER_SECRET = '-----------------1ZGHgBRz6aEr4YhUVuO84CuEV'
ACCESS_KEY = '-----------------4MCjSkny9Y6rJj5I32ulXctISQF'
ACCESS_SECRET = '------------------------cDadRY3He5Kv6CXVuqy2Dh'
api = Twython(CONSUMER_KEY,CONSUMER_SECRET,ACCESS_KEY,ACCESS_SECRET)
api.verify_credentials()
host = "-.--.---.196"
ftp_user = "ftp_user"
ftp_password = "ftp_password"
con = ftplib.FTP(host, ftp_user, ftp_password)
con.cwd("/mowmylawn.ru/webcam")
a={0:'Всем привет! Какой чудесный день!',1:'Опять трудовые будни. Скорее бы выходной!',2:'Верблюд может не пить две недели...А у меня бензин заканчивается',3:'Ну почему опять я должен заниматься газоном?',4:'Улыбаемся, снимает скрытая камера!',5:'Работа не волк, а вот я могу в лес убежать :)) !',6:'Врум, врум...стригу газон!',7:'Не плачь, на Марсе тоже жизни нет.',8:'Эйнштейн был прав: выходные - понятие относительное',9:'Самое сложное — не знать, правильно ли ты сделал...',10:'Меня одну волнует этот вопрос: Когда у меня отпуск?',11:'Начинаю обработку территории',11:'Кто-куда, а я работать',12:'Судьба - это то, что мы получаем в результате наших решений и поступков.',13:'— У тебя не все дома! — Конечно, я же на работе!',14:'Хозяин, батарейки на исходе! Сжалься...:(((!',15:'Хочешь меня сделать? robogazon.ru',16:'Всё возможно, пока не сделан выбор!'}
def twit():
image = cam.get_image()
pygame.image.save(image,'webcam.jpg')
photo = open('webcam.jpg','rb')
b = random() * (len(a)-1)
b = int(round(b,0))
#api.upload_media(media=photo)
#api.update_status(status=a[b])
api.update_status_with_media(media=photo, status=a[b])
def ftpimg():
image = cam.get_image()
pygame.image.save(image,'webcam.jpg')
photo = open('webcam.jpg','rb')
send = con.storbinary("STOR "+ 'webcam.jpg', photo)
while 1 :
ftpimg()
b = random() * (500)
b = int(round(b,0))
if b==107:
twit()
con.close
Второй скрипт по http получает на сервере текущую команду для действия и отправляет эту команду по serial на arduino.
- «S» — стоп
- «F» — вперед
- «B» — назад
- «L» — влево
- «R» — вправо
- «W» — включить двигатели кошения
- «w» — выключить двигатели кошения
- «V» — включить сигнал
- «v» — выключить сигнал
import serial,time
import urllib3
http = urllib3.PoolManager()
ser = serial.Serial("/dev/ttyUSB0",9600)
ser.writelines("S");
olddata=0
countolddata=0
while 1 :
r = http.request('GET', 'http://mowmylawn.ru/1.php')
if r.data!=olddata:
olddata=r.data
countolddata=0
else:
countolddata+=1
if countolddata>20:
ser.writelines("S")
else:
ser.writelines(r.data)
ser.close()
con.close
Веб-сервис
На bootstrap накидал страничку. На странице два .jpg файла, которые обновляются по мере загрузки. Факторов, влияющих на задержки в управлении и телеметрии много, это и Ваша скорость соединения, и канал на сервере, и канал у меня дома.
<script>
setInterval(function(){
var im1='http://mowmylawn.ru/webcam/webcam.jpg?s='+Math.random();
var im2='http://kosmos-podolsk.ru/webc1.jpg?s='+Math.random();
var tmpImg = new Image() ;
tmpImg.src = im1 ;
tmpImg.onload = function() {
$("#w1dd").attr('src',im1 );
} ;
tmpImg.src = im2 ;
tmpImg.onload = function() {
$("#w2dd").attr('src',im2 );
} ;
}, 200);
</script>
Максимальная частота обновления, которую удалось добиться мне:
- камера на ровере 4 Гц;
- камера на доме 3 Гц.
База данных Mysql состоит из 2 таблиц, в первой хранится одна пара ключ/значение, это команда для робота. Вторая таблица — users.
Когда Вы встаете в очередь на сайте — отправляется ajax get запрос на добавление пользователя, в базу заносится запись с отметкой timestamp, вашим ip и сгенерированным ключом для управления.
Управлять косилкой одновременно может только один человек (кроме меня) — это пользователь с самым маленьким timestamp. Когда приходит Ваша очередь и Вы начинаете управлять косилкой — в базу заносить timestamp начала управления, каждому отведено на управление 60 сек…
Каждый раз когда Вы наводите на кнопки управления отправляется ajax get запрос с командой и Вашим ключом на управление, при этом проверяется разница между текущим временем и временем, когда Вы начали «игру», если разница больше 60 сек, для Вас игра заканчивается, Ваша запись удаляется из базы и Вы опять можете встать в очередь, «игра» переходит к следующему игроку.
Промо видео
Мой первый пост на reddit. Попробуйте управление mowmylawn.ru. В случае большой очереди или хабраэффекта — прошу понять и простить.
P.S.: На забывайте, что Вы можете принять участие в совместном проекте по разработке фитнес-трекер для ударных видов спорта KickBrick. В команде ждут Вас!
Автор: webzuweb