Дроны - как керосин. Они есть везде.
Еще года два назад это было просто еще одно интересное видео на Ютубе. Год назад они вдруг оказались в интернет магазинах. Затем просочились в рекламу на ТВ, и вот теперь - они есть и в OpenComputers!
Пришла пора с ними разобраться.
1. Матчасть
Дрон, в данном случае - квадрокоптер, это беспилотный летающий аппарат, приводимый в движение двумя парами горизонтальных винтов. Приостанавливая вращение винтов с одного боку, дрон двигается в сторону (стрейф).
Эти винты вращаются в разном направлении (два - по часовой срелке и два - против), за счет чего дрон не нуждается в стабилизирующем хвостовом пропеллере (как вертолет). За счет этого же он и разворачивается в воздухе, замедлив вращение однонаправленной пары винтов.
Дрон обладает небольшой массой, для экономии энергии, которой у него не много (на 10-30 минут полета в среднем).
(с) Википедия
2. Дроны и OpenComputers
Приблизительное изображение дрона в OpenComputers =):
В мире Майнкрафта дрон представляет из себя "сущность" (Entity). Это значит, что он обладает возможностями мобов Майнкрафта. (В то время как робот - это блок.)
Его можно сдвинуть с места толкая. Он умеет пролетать сквозь двери и калитки (в отличии от робота). Он движется не последовательно, из блока в блок, а из точки в точку. Причем маршрут может лежать по диагонали.
Конечно, движется он по кратчайшей линии, и если на пути окажется стена - дрон столкнется с нею.
Программирование дрона как две капли воды похоже на программирование микроконтроллера. Вы точно так же записываете программу на EEPROM, и при необходимости меняете ее на верстаке.
Только в отличии от контроллера, вам становится доступен новый
компонент: drone
.
Подробнее об командах дрона можно узнать здесь:
OpenComputers/Дрон.
(Или здесь: ocdoc.wiki (англ.))
3. План
Нужна какая-нибудь несложная задача, для целей эксперимента. Используем
программку send
из предыдущего поста, для удаленного
управления.
Зальем ее на планшет.
А дрон пусть... носит свиней. Будем оригинальными и непоследовательными.
- Команда
add X Y Z Name From
. Добавляем точку Name к маршруту, цепляя ее к точке From. Зададим дрону последовательность точек, которые образуют граф - безопасные маршруты. - Команда
catch
- дрон ловит свинью. - Команда
drop
- дрон выпускает свинью. - Команда
to X
- дрон летит в точкуХ
.
Для начала не будем особо заморачиваться с графом маршрутов. Это будет простое неориентированное дерево.
Примерно такое:
4. Строим полигон
Построим что-нибудь подходящее для тестов. Отметим ключевые точки будущего графа красными блоками.
А синий блок - будет стартовой площадкой дрона.
Поскольку я играю без модов на энергию, мой планшет и дрон будут работать вечно. И я не заморачиваюсь станцией подзарядки.
Иначе, к схеме выше было бы необходимо добавить станцию, где дрон мог бы зарядить аккумулятор.
5. Пишем программу
Скрипт для удаленного управления скопипастим из прошлого поста, подправим, чтобы умела отправлять несколько переменных и зальем на планшетик, для удобства.
(Для этого, соберите планшет - не забудьте клавиатуру и видеокарту! -
положите его в зарядник и запустите с подключенного компа команду install
.
Укажите адрес винчестера планшета - и все, что было у вас на компе
автоматически загрузится в планшет, включая даже ваши собственные
программы.)
local com = require('component') local modem = com.modem local args = {...} modem.broadcast(27, table.unpack(args)) io.write("Message: ") print(table.unpack(args))
Далее - более сложная часть. Программа дрона. Программа предназначена
для EEPROM. Значит соблюдаем те же правила: используем computer
,
component
и API имеющихся у дрона компонентов. Включая его родной
компонент drone
.
В нашем случае, дрон вооружен апргейдом-лассо (leash
) и беспроводной
сетевой картой (modem
) для связи.
Стоит отметить, что процесс отладки программы (по крайней мере в текущем
билде мода) достаточно неудобен. В случае ошибки дрон отказывается
включиться, издав тонкий писк, и не выводя никакой информации. Получить
отчет об ошибке при помощи анализатора не выйдет - ведь Shift
+ПКМ
просто
снимает дрона. Автор обещал в скором времени это исправить. Ну а пока -
помучаемся.
Отредактировать чип в стороннем редакторе, не вынимая его из дрона тоже
не выйдет. В отличии от файловых систем, которые имеют удобную папку
вида /saves/World/opencomputers/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/
,
чипы EEPROM хранят свой код в NBT тегах предмета. Этим же обусловлено
и ограничение размера кода в 4 килобайта.
5.1. Основная часть
Это цикл который ждет указаний, а затем запускает соответствующую функцию.
drone = component.proxy(component.list("drone")()) modem = component.proxy(component.list("modem")()) leash = component.proxy(component.list("leash")()) modem.open(27) route = {} path = {} current = "" while true do name, _, sender, _, _, message, x, y, z, point, from = computer.pullSignal(1) if name == "modem_message" then if message == 'add' then add(tonumber(x), tonumber(y), tonumber(z), point, from) if current == "" then current = point end elseif message == 'to' then to(x) elseif message == 'catch' then catch() elseif message == 'drop' then drop() end end if #path > 0 and drone.getOffset() < 1 then drone.move(route[path[#path]].x-route[current].x, route[path[#path]].y-route[current].y, route[path[#path]].z-route[current].z) current = path[#path] path[#path] = nil end end modem.close()
Чтобы облегчить себе жизнь (и тестирование BIOS), вы можете сделать так:
напишите заглушку для компонента drone
(и других, если надо),
вроде этой.
Просто скопируйте в папку на компьютере, где вы пишете программу для дрона.
Затем измените первые строки программы следующим образом:
component = require('component') computer = require('computer') drone = require('drone') modem = component.modem -- leash = component.proxy(component.list("leash")())
Затем добавьте в цикл условие выхода по нажатию кнопки:
if name == 'key_down' then break end
И вы можете просто запустить вашу программу для дрона на компьютере.
Разумеется полноценной эмуляцией дрона тут и не пахнет, зато очень удобно отслеживать глупые синтаксические и логические ошибки.
Как устроен код основного цикла?
Переменная route
- хранит таблицу "вейпоинтов" (waypoints).
Это вершины графа и информация о связях между ними.
Переменная path
- хранит путь от текущей вершины до цели.
Переменная current
- отмечает текущее местоположение дрона в графе.
В цикле мы читаем получаемые сообщения и вызываем соответствующие функции. Первая переданная вершина считается дроном текущей.
Во второй части цикла происходит проверка. Если путь до цели - не пуст
(это значит, что дрону надо куда-то лететь) и дрон уже долетел до
текущей вершины (getOffset()
), то программа берет следующую вершину
из path
, отправляет дрона к ней и объявляет ее текущей.
5.2. Функции-команды
Теперь последовательно добавим функции для каждой команды.
function add(x, y, z, name, from) route[name] = {x=x, y=y, z=z, link = {}} if from ~= nil then if route[name] == nil or route[from] == nil then drone.setStatusText("Error!") else table.insert(route[name].link, from) table.insert(route[from].link, name) end end end
Тут все просто. Пишем вершину в список. Если он связана с другой
вершиной (from ~= nil
), то в специальную табличку link
заносим
две связи: из name
в from
, и из from
в name
.
function search(target, point, prev) for key, name in pairs(route[point].link) do if name == target then table.insert(path, point) return true end end for key, name in pairs(route[point].link) do if name ~= prev then if search(target, name, point) then table.insert(path, point) return true end end end return false end function to(name) path = {} table.insert(path, name) search(name, current) end
Функция to
обнуляет старый путь (на всякий случай), затем вставляет
в него цель пути (name) и запускает функцию search, которая рекурсивно
ищет и записывает остальные промежуточные вершины на маршруте от name
до current
(текущей локации).
Функция search
сделана достаточно примитивно (возможно вы предложите
более эффективный способ?). Поскольку мы договорились, в целях
упрощения использовать граф-дерево (не содержаший петель), от любой
точки к другой существует один и только один маршрут, который функция
и находит перебором связанных вершин.
function catch() for c = 2, 5 do if leash.leash(c) then return true end end return false end function drop() leash.unleash() end
Тут все элементарно.
6. Подготовка
Пишем программу на дрона, заряжаем планшет и выдвигаемся в зону действий. Дрона ставим на синий куб (стартовая площадка) и включаем.
После уточнения на местности, составляем карту вейпоинтов и строим на бумажке будущий граф:
Для каждого загона добавлены две точки - name
и name_up
. Основные
"трассы" дрона лежат на высоте в 6 блоков. А в каждом загоне спускаются
к земле. (Чтобы заарканить животное, выстреливая лассо вбок, дрону
желательно находиться на одном уровне с жертвой).
С планшета вносим координаты в память дрона. Примерно так:
Главное - не ошибиться. Т.к. в код не была добавлена защита "от дурака" =)
Алгоритм позволяет добавлять вершину "на лету". В любой момент вы можете добавить еще одну ветку к схеме.
Теперь все готово к тесту.
7. Запуск
Все готово.
Проверим, как он двигается.
Введем send to sheeps
в консоль планшета. Дрон уверенно поднимается в
воздух и опускается в загоне в овцами.
Теперь введем send to pigs
. Функция search
снова вычислит путь и
робот переместится в указанную вершину:
Функции catch и drop тоже работают штатно =)
Хотя и не лишены некоторых глюков (ведь физика веревки не просчитывается):
8. Итоги
- Дрон - любопытная штуковина.
- Полный код прошивки. использованный в этом посте - здесь.
- Навигация по вейпоинтам - интересный и очень распространенный способ организации сложного движения. Схему можно усложнить - опционально добавлять только одну связь в таблицу link - тогда получатся ребра с односторонним движением. Добавить петли, оптимизировать поиск кратчайшего пути.
Еще можно облегчить правление дроном - хранить все команды для конкретной задачи в виде файла-скрипта, который запускать одной командой и т.д.
Enjoy!