В последнее
время наш блог молчит и может показаться,
что «Карандаш и Самоделкин» все, сдулись.
На самом деле работа кипела, а отсутствие
публикуемых материалов было связано с
тем, что проекты раз за разом становятся
все сложнее и требуют все больше времени
на реализацию. Последние наши разработки
были так или иначе связаны с нейросетями,
поэтому логично было за это время
подтянуть скиллы по этой теме, что и
было сделано – был пройден ряд онлайн
курсов по и даже пара оффлайновых, из
которых особенно понравилась «Зимняя
школа машинного обучения» (MachineLearningWinterSchool).
Проект,
работа над которым шла больше 4 месяцев,
мы назвали Sudoku Hunter. Изначально была идея
собрать робота по мотивам NXTSudokuSolver,
однако постепенно она трансформировалось
в нечто иное, на наш взгляд не менее
интересное.
Итак,
Sudoku Hunter – это пистолет с EV3,
веб-камерой и RaspberryPi
на борту, из которого можно «стрелять»
по математическим головоломкам судоку,
получая на экране EV3-блока
их решение. В алгоритмической части
используются технологии машинного
зрения и машинного обучения, веб-технологии
для создания отладочной видеоконсоли.
Если
кто не помнит, судоку - головоломка на
бумаге, игровое поле которой представляет
собой квадрат размером 9×9, разделённый
на меньшие квадраты со стороной в 3
клетки. Таким образом всё игровое поле
состоит из 81 клетки. В них уже в начале
игры стоят некоторые числа (от 1 до 9),
называемые подсказками. От игрока
требуется заполнить свободные клетки
цифрами от 1 до 9 так, чтобы в каждой
строке, в каждом столбце и в каждом малом
квадрате 3×3 каждая цифра встречалась
бы только один раз.
Конструкция
нашего робота не особенно замысловата,
поэтому инструкцию по сборке мы решили не
делать – вы легко соберете что-то
подобное просто глядя на фото. Мы
использовали следующие компоненты:
Блок
LegoMindstromsEV3
и строительные детали из этого набора
RaspberryPi
Веб-камера
Sony Playstation
Eye
Wi-fi
адаптер для связи EV3
с RPi
Powerbank
для питания RPi
Программ,
которые необходимы для работы робота
- несколько, все они написаны на Python:
Программа
для EV3 - отвечает за вывод информации
на экран блока, связь с Raspberry Pi, передачу
на RPi
состояния датчика-кнопки
Программа для
RaspberryPi
- отвечает за обработку кадров с камеры,
распознаванием цифр с использованием
обученной нейросети, последующим
решением распознанного судоку, связь
с EV3,вывод
вспомогательной и отладочной информации
на веб-консоль,
Программа
для RaspberryPi,
предназначенная для создания дата сета
для обучения нейронной сети.
Программа
для ПК, используется для создания и
обучения нейросети на основе
подготовленного дата сета
Основная
программа для RaspberryPi
работает по следующему алгоритму:
Подключение
библиотеки машинного зрения OpenCV
Подключение
библиотек машинного обучения keras(tensorflow), sklearn
Процесс
1:
Взятие
кадра с камеры
Преобразование
изображения из BGR в HSV
Наложение
маски для фокусировки
на значимых элементах изображения
Запись
результирующего кадра в объект в памяти
Соединение
с EV3с
помощью протокола socket
Основной
цикл:
Чтение
кадра из объекта в памяти
Поиск
в изображении с наложенной
маской объектов, напоминающих
квадрат
Передача
данных о предполагаемом расположении
квадрата на EV3,
для вывода на экран блока
Приём
данных с EV3
о состоянии кнопки
Если
кнопка нажата:
Процесс
3:
Запуск
веб-сервера (используется модуль Flask)
Соединение
с RaspberryPi с
помощью протокола socket
Получение данных
о предполагаемом расположении квадрата
Подключение
библиотеки машинного зрения OpenCV
Подключение
библиотек машинного обучения keras(tensorflow), sklearn
Процесс
1:
Взятие
кадра с камеры
Преобразование
изображения из BGR в HSV
Наложение
маски для фокусировки
на значимых элементах изображения
Запись
результирующего кадра в объект в памяти
Соединение
с EV3с
помощью протокола socket
Чтение
кадра из объекта в памяти
Поиск
в изображении с наложенной
маской объектов, напоминающих
квадрат
Передача
данных о предполагаемом расположении
квадрата на EV3,
для вывода на экран блока
Приём
данных с EV3
о состоянии кнопки
Если
кнопка нажата:
Запуск
веб-сервера (используется модуль Flask)
Вывод
информации на веб-сервер - видеоконсоль
для удобства отладки
Подключение
библиотек машинного обучения
Загрузка
дата сета изображений
Разбиение
дата сета на учебные
и тренировочные данные
Создание
нейронной сети с заданными параметрами
Обучение
нейронной сети
Сохранение
модели
Процесс
2:
Изображение
обрабатывается и судоку «нарезается»
на 81 квадрат в каждом из которых одна
клетка головоломки
Каждый
из фрагментов проверяется на наличие
в нём цифры и если цифра обнаружена,
то клетка подаётся на распознавание
нейронной сети
Распознанная
цифра заноситься в массив. Когда массив
заполнен всеми цифрами, он подаётся в
алгоритм, решающий судоку
Массив содержащий
решённую головоломку передаётся на
EV3
для вывода на экран блока
Вывод
информации на веб-сервер - видеоконсоль
для удобства отладки
Программа на
EV3
работает по следующему алгоритму:
Вывод
контура квадрата на экран для
визуального контроля (размер квадрата,
угол разворота, степень его трапециевидности)
Передача
состояния датчика-кнопки
на Rpi
Если
от RPi
получен массив данных с решенной
головоломкой - вывод решённого судоку
на экран блока
Программа
создания дата сета для обучения нейронной
сети на RaspberryPi
имеет следующий алгоритм:
Процесс
2:
Изображение
обрабатывается и судоку «нарезается»
на 81 квадрат в каждом из которых одна
клетка головоломки
Каждый
квадрат сохраняется в папку, формируя
дата сет
Процесс 3:
Программа
на ПК, предназначенная для создания и
обучения нейросети на основе подготовленного
дата сета работает так:
Стандартный
алгоритм обучения нейронной сети мы
усовершенствовали
путём запуска функции обучения не на
заданное количество
эпох, а по одной эпохе, с проверкой
результата на проверочной выборке после
каждой. Если результат ухудшается
несколько эпох подряд — прекращаем
обучения с ошибкой. Если результаты на
тестовой выборке выше 95% -
прекращаем
обучения, проверяем модель на отдельной
выборке (которую она ещё не видела ни
при обучении,
ни
при проверках после каждой эпохи).
Почти сотня роботов, которых мы построили в рамках нашего проекта, до сих пор действовали строго по заложенным в них алгоритмам. Конечно, со временем и благодаря наработанному опыту поведение наших роботов становилось все сложнее, стало все более напоминать поведение разумных машин, но весь этот разум - зачастую витиеватые взаимосвязи условных конструкций и структур данных - не позволяли им выйти за рамки заложенных в программы реакций на те или иные события. Иными словами - изменились условия, роботу нужно действовать по другому чтобы сохранить целевой результат - извольте, правьте код, выстраивайте взаимосвязи, описывайте их в условных конструкциях - робот послушно их выполнит.
Бывают задачи, в которых эти взаимосвязи не так-то просто выстроить и заложить в код или они могут быть вообще не известны. Давайте рассмотрим одну из таких задач, упростив ее решение настолько, насколько это возможно с тем, чтобы уловить суть непривычных пока для нас алгоритмов.
Наверное все мы в детстве любили качаться на качелях. Как вы думаете, если ребенка впервые посадить на качели и не показать ему как на них раскачиваться (и не давать соответственно подсмотреть как это делают другие), получится у него их раскачать, сохранять и контролировать скорость раскачивания? В его мозге нет устойчивых взаимосвязей и образов того как ему это сделать. Он начнет пробовать совершать произвольные действия, если качели начнут раскачиваться в мозг пойдет визуальная и вестибулярная обратная связь и по тем действиям, которые он только что предпринимал, начнут формироваться новые нейронные цепочки, связанные с "раскачать качели". Если какие то действия наоборот начнут тормозить раскачавшиеся качели, например ребенок отклонился не в ту сторону - он "запомнит" что так делать не нужно, соответствующие связи будут ослаблены или созданы новые.
Похожим образом работает и искусственная нейросеть, которую мы создадим в нашем сегодняшнем проекте.
Для начала соберем конструкцию. Качели мы построили на базе LEGO Mindstorms EV3, использовав несколько дополнительных деталей из ресурсных наборов. Инструкцию по сборке наиболее сложной части конструкции - качающихся человечков - в формате LEGO Digital Designer вы можете скачать по ссылке. Остальную часть качелей каждый без труда сможет достроить из тех деталей, которые будут у него под рукой.
Человечки из больших EV3-моторов способны наклоняться, смещая центр тяжести качели. Качели закреплены на раме свободно, поэтому важно чтобы они были как можно более симметричными. Для робота потребуется гироскоп, на основе его показаний будет происходить самообучение нейронной сети.
Посмотрим на качели сбоку. изначально человечки "не знают", в какую сторону им нужно отклоняться (влево или вправо) чтобы качели начали раскачиваться. Хороший способ объяснять вероятности их наклона в ту или иную сторону используя терминологию спичечных коробков и камушков черного и белого цвета. Пусть у нас есть два коробка, это два нейрона нашей нейронной сети. в каждом из них в случае необученной сети, лежит по 1 камушка каждого цвета. Таким образом вытащить камушек белого и черного цвета случайным образом можно с вероятностью 50%, соответственно не обученная сеть с равной вероятностью будет отклонять человечков влево и вправо в любой ситуации.
Первый коробок соответствует состоянию когда качели качаются влево, второй - вправо. Если "вытащили" белый камень, человечкам нужно отклониться влево, если черный - вправо. Камень после того как мы его вытащили помещается на место, где он и был.
Понятно, что в самом начале человечки будут дергаться туда-сюда хаотично. Введем блок самообучения. Качество обучения будем оценивать до достигнутой качелями средней скорости без учета знака, по данным гироскопа. Если скорость растет - предпринятая попытка раскачать качели верная, падает - "человечки" ошиблись.
Алгоритм таков:
1) Смотрим куда качаются качели (по данным гироскопа) - влево или вправо и выбираем коробок - первый или второй. Следует заметить что мы не рассматриваем ситуацию когда качели имеют нулевую скорость, например при старте, развивая алгоритм дальше нужно добавить третью коробку с камушками, но пока обойдемся без нее.
2) Вытаскиваем наугад камень из выбранной коробки. Смотрим его цвет и кладем обратно. если камень белый - даем человечкам команду отклониться влево, если черный - вправо.
3) Оцениваем скорость качелей. Если она выросла - кладем в коробку, из которой мы брали камень еще один камень такого же цвета. Если скорость упала - убираем из коробки камень то го цвета, который был вытащен. Следует заметить что последний камень каждого цвета лучше не убирать, пусть остается.
4) Переходим к п.1
В процессе обучения возможна интересная ситуация - неподвижные качели можно раскачать даже неправильными движениями, но только до какого-то предела, если попытки предпринимать и дальше, то они наоборот замедлят раскачивание. В реальной работе робота ситуация действительно проявляется довольно часто, робот раскачивается до какого-то предела (небольшого), но дальше процесс не идет. Дальнейшие попытки раскачать качели приводят к их полной остановке и далее процесс обучение начинается снова, уже в "верном" направлении. И заметьте, это все без вмешательства человека - вполне разумная машина!
# импортируем модуль для работы с железом EV3 from ev3dev.ev3 import * # импортируем модуль математики from math import * # импортируем модуль для работы со временем from time import * # импортируем модуль генератора случайных чисел from random import random # импортируем модуль многопоточности import threading # начальная скорость моторов ("человечков") speedB = 0 speedC = 0 # текущая средняя скорость движения качелей moving_average = 0 # предыдущая средняя скорость движения качелей moving_average_old = 0 # Позиция человечков -100..100 pos = 0 # направление движения качелей state_gyro = 0 # описание нейрона сети class brain(): def __init__(self): self.Black = 1 self.White = 1 # создаем пару нейронов BrainLeft = brain() BrainRight = brain() # объект для отслеживания кнопок на блоке btn = Button() # объекты для моторов B = LargeMotor('outB') C = LargeMotor('outC') # объект для работы с гироскопом gyro = GyroSensor("in4") # устраняем дрейф гироскопа и ставим его в режим измерения скорости gyro.mode = "GYRO-RATE" gyro.mode = "GYRO-ANG" gyro.mode = "GYRO-RATE" # человечков - в исходную позициюsleep(1) B.run_forever(speed_sp=80) C.run_forever(speed_sp=80) sleep(3) B.stop(stop_action="brake") C.stop(stop_action="brake") sleep(1) B.reset() C.reset() B.run_to_rel_pos(position_sp=-120, speed_sp=80) C.run_to_rel_pos(position_sp=-120, speed_sp=80) while any(C.state): sleep(0.1) B.stop(stop_action="brake") C.stop(stop_action="brake") sleep(1) B.reset() C.reset() # готов к работе! Sound.speak('Ready').wait() stop = False # П-регулятор для управления человечками def reg(): while not stop: speedB = ((-1*pos)-B.position)*8 speedC = (pos-C.position)*8 if(speedB > 900): speedB = 900 if(speedB < -900): speedB = -900 if(speedC > 900): speedC = 900 if(speedC < -900): speedC = -900 B.run_forever(speed_sp=speedB) C.run_forever(speed_sp=speedC) sleep(0.01) # Запускаем регулятор в параллельном потоке t = threading.Thread(target=reg) t.daemon = True t.start() # Максимальная достигнутая скорость max_speed = 0 while not stop: # вылет по кнопке "назад" на блоке stop = btn.backspace # скорость по показаниям гироскопа state_gyro_speed = gyro.value() if(state_gyro_speed != 0): # текущее направление движения качелей state_gyro = state_gyro_speed/abs(state_gyro_speed) if(state_gyro > 0): # если влево, и вытащили черный камень if(random() <= (BrainLeft.Black/(BrainLeft.Black + BrainLeft.White))): pos = -100 # если белый else: pos = 100 else: # если вправо, и вытащили черный камень if(random() <= (BrainRight.Black/(BrainRight.Black + BrainRight.White))): pos = -100 # если белый else: pos = 100 moving_average_old = moving_average
# ждем перед оценкой sleep(0.25) moving_average = abs(state_gyro_speed)*0.1 + moving_average*0.9 if(state_gyro > 0): # если скорость не упала при движении влево if(moving_average >= moving_average_old): if(pos<0): BrainLeft.Black += 1 else: BrainLeft.White += 1 else: # если скорость упала при движении влево if(pos<0 and BrainLeft.Black > 1): BrainLeft.Black -= 1 elif(pos>0 and BrainLeft.White > 1): BrainLeft.White -= 1 elif(state_gyro < 0): # если скорость не упала при движении вправо if(moving_average >= moving_average_old): if(pos<0): BrainRight.Black += 1 else: BrainRight.White += 1 else: # если скорость упала при движении вправо if(pos<0 and BrainRight.Black > 1): BrainRight.Black -= 1 elif(pos>0 and BrainRight.White > 1): BrainRight.White -= 1 # выводим состояние нейронной сети print("[[ " + str(BrainLeft.Black) + ", " + str(BrainLeft.White) + "][ " + str(BrainRight.Black) + ", " + str(BrainRight.White) + "]]",moving_average) # если скорость выросла на 25, фиксируем новый рекорд if moving_average > max_speed + 25: print("[[ " + str(BrainLeft.Black) + ", " + str(BrainLeft.White) + "][ " + str(BrainRight.Black) + ", " + str(BrainRight.White) + "]]",moving_average) max_speed = moving_average # если скорость выросла до 200 - обучение завершено if max_speed < 200: Sound.speak('New Record') else: Sound.speak('Learning Complete') Sound.beep().wait() B.stop(stop_action="brake") C.stop(stop_action="brake") # завершение работы Sound.speak('Stop').wait()
Год назад мы перешли с графического программирование нашего робота LEGO Mindstorms EV3 на текстовое и первым языком программирования, который мы освоили, стал EV3 BASIC. Нам очень понравились его простота, отзывчивость к первым, зачастую неумелым попыткам выжать из него что-то большее, то, что мы могли получить на графическом "леговском" EV3-G. За этот год мы создали с использование BASICа целый ряд проектов (NAVIDOZ3R, Music Station, Телеграф, НУ ПОГОДИ, Саймон сказал), которые, как нам кажется, неплохо раскрыли возможности этого языка. Программы, написанные не нем работают существенно быстрее программ на "языке из кубиков". Бейсик не навязывает "хороший стиль" программирования, он дает свободу программисту - в этом его основная фишка.
Однако чем более сложными становились наши проекты с использованием EV3 BASIC, тем все более отчетливо мы ощущали его ограничения:
все переменные - глобальные
так же как и в EV3-G нет возможности работать с многомерными массивами без использования костылей
нельзя создать функцию с передачей в нее параметров и возвратом из нее результата
проблемы с поддержкой датчиков сторонних производителей
Хорошей альтернативой и следующей ступенькой в полноценном программировании EV3 традиционно считается ROBOTC, однако это достаточно дорогая ступенька, данная среда разработки далеко не бесплатна. Кроме этого ROBOC требует перепрошивки блока EV3 на свою прошивку, это не для всех приемлемо.
В мире opensource существует несколько свободных альтернатив для программирования платформы EV3 с использованием "взрослых" языков. Это LeJOS и язык Java, Monobrick с языком С# и ev3dev с целым рядом языков - Python, C, C++, JavaScript. Go.
Мы решили начать с ev3dev и языка Python, так как ходили слухи что язык этот имеет достаточно низкий порог вхождения подобно Бейсику, при этом не имея присущих ему недостатков. Еще на летних каникулах мы проштудировали замечательную детскую книгу по Python под названием "Hello World! Занимательное программирование" (Картер Сэнд, Уоррен Сэнд, 2016). На примере создания простейших игр книга действительно занимательно погружает читателя в основы языка Python.
Начав разбираться с ev3dev нам показалось, что информации из книги для погружения в Python не достаточно и в поисках ее дополнительных источников мы набрели на крутые онлайн-курсы на портале Stepic.org
В процессе прохождения первого курса, в попытках решения учебных задач с Python на EV3 пришло понимание того, что базовые навыки работы в ОС Linux пришлись бы юным робототехникам как нельзя кстати. Курс на том же Stepic под названием Введение в Linux оставил только положительные эмоции и дал массу полезной информации, в том числе и для размышления.
Очень полезным источником информации для нас стал сайт по EV3 Python. Его автор, Nigel Ward, уже помогал нам год назад в адаптации EV3 BASIC для русскоязычных робототехников. Его материалы мы взяли за основу и в этот раз. Мы подготовили русскоязычное руководство по Python для EV3, которое состоит из двух частей:
В первой части - описание процесса установки и первоначальной настройки ev3dev. Во второй - справочник по "командам" и примеры программ на Python для платформы EV3.
Кроме этого, так же как и в прошлом году мы решили несколько учебных задачек, некоторые из которых встречаются в нашем онлайн-задачнике по робототехнике.
Исходные кода всех программ, а также инструкцию к роботу, способному их решить вы можете скачать по ссылке.
1. Робот, поворачиваясь вокруг своей оси сканирует пространство, после чего выводит на экран "карту-радар" в виде лепестковой диаграммы.
# подключаем модуль поддержки оборудования EV3 from ev3dev.ev3 import * # из модуля time будем использовать задержку - sleep from time import sleep # кроме это нам понадобится модуль математики import math # Это скорость разворота робота, в градусах/сек speed = 50 # переменная для хранения значений с датчика-гироскопа GyVal = 0 # создаем объект Gyro связанный с датчиком-гироскопа на 1 порту Gyro = GyroSensor("in1") # и объект IR, связанный с ИК-датчиком на порту 4 IR = InfraredSensor("in4") # нам понадобится пара моторов, порты A и D mA = LargeMotor('outA') mD = LargeMotor('outD') # устанавливаем гироскоп в режим измерения угла Gyro.mode = "GYRO-ANG"
# создаем объект lcd для работы с экраном блока EV3 lcd = Screen() # рисуем в буфере пару кругов (это скелет карты) lcd.draw.ellipse(( 84, 59, 94, 69)) lcd.draw.ellipse(( 25, 0, 153, 128)) # выводим инфу из экранного буфера на экран lcd.update() # создаем список (массив) для хранения карты g = [0 for i in range(360)] # запускаем робота на вращение вокруг своей оси mA.run_forever(speed_sp=speed) mD.run_forever(speed_sp=-1*speed) # во время вращения заполняем массив g данными # с ИК-датчика с привязкой у направлению (гироскоп) while Gyro.value() < 360: GyVal = abs(Gyro.value()) g[GyVal] = IR.value()*0.7 # после полного круга останавливаемся mA.stop(stop_action="hold") mD.stop(stop_action="hold") # выводим карту из массива в экранный буфер # в виде лепестковой диаграммы for i in range(90): lcd.draw.line(( 89, 64, 89+g[i]*math.sin(math.radians(i)), 64-g[i]*math.cos(math.radians(i)))) lcd.draw.line(( 89, 64, 89+g[i+90]*math.cos(math.radians(i)), 64+g[i+90]*math.sin(math.radians(i)))) lcd.draw.line(( 89, 64, 89-g[i+180]*math.sin(math.radians(i)), 64+g[i+180]*math.cos(math.radians(i)))) lcd.draw.line(( 89, 64, 89-g[i+270]*math.cos(math.radians(i)), 64-g[i+270]*math.sin(math.radians(i)))) # выводим информацию из буфера на экран lcd.update() # издаем какой-то звук Sound.beep() # и замираем на 10 секунд чтобы успеть посмотреть карту sleep(10)
2. Плавное изменение цветов и яркости подсветки по нажатию кнопок на блоке, например вверх-вниз изменяют яркость, влево-вправо изменяет цвет, нажатие в центр запускает автоматические переливы. Голосом озвучивается изменения режимов.
# подключаем модуль поддержки оборудования EV3 from ev3dev.ev3 import * # из модуля time будем использовать задержку - sleep from time import sleep # объект для работы с кнопками на блоке btn = Button() tr = 1 # список цветов подсветкиcolor = [Leds.RED, Leds.GREEN, Leds.AMBER, Leds.ORANGE, Leds.YELLOW] cs = 0 # выключаем подсветкуLeds.all_off()
# текущая яркость подсветки on = 127 # цикла - пока не нажата кнопка назадwhile not btn.backspace: # если нажата кнопка влево if btn.check_buttons(buttons=['left']): cs=cs-1 if cs<0: cs=4 # проговариваем ее название # и изменяем цвет с выбранной яркостью Sound.speak("Button left") Leds.set_color(Leds.LEFT, color[cs], on/255) Leds.set_color(Leds.RIGHT, color[cs], on/255) sleep(0.5) # тоже самое для кнопки вправо elif btn.check_buttons(buttons=['right']): cs=cs+1 if cs>4: cs=0 Sound.speak("Button right") Leds.set_color(Leds.LEFT, color[cs], on/255) Leds.set_color(Leds.RIGHT, color[cs], on/255) sleep(0.5) # если нажата кнопка вверх if btn.check_buttons(buttons=['up']): # увеличиваем яркость подсветки on=on+10 if on>240: Sound.speak("Maximum") on=255 Leds.set_color(Leds.LEFT, color[cs], on/255) Leds.set_color(Leds.RIGHT, color[cs], on/255) sleep(0.1) # если нажата кнопка вниз elif btn.check_buttons(buttons=['down']): # уменьшаем яркость on=on-10 if on<10: Sound.speak("Minimum") sleep(2) on=0 Leds.set_color(Leds.LEFT, color[cs], on/255) Leds.set_color(Leds.RIGHT, color[cs], on/255) sleep(0.1) # при нажатии центральной кнопки if btn.check_buttons(buttons=['enter']): Sound.speak("Random color") tr = 1 # цикл пока не нажата кнопка назад while not btn.backspace or tr!=0: Leds.set_color(Leds.RIGHT, Leds.RED,0) Leds.set_color(Leds.LEFT, Leds.GREEN,0) # "переливы" цветов и яркости подсветки for i in range(0,255,5): Leds.set_color(Leds.LEFT, Leds.GREEN,i/255) if btn.check_buttons(buttons=['backspace']): tr = 0 if tr == 0: break for i in range(255,0,-5): Leds.set_color(Leds.RIGHT, Leds.ORANGE,(255-i)/255) Leds.set_color(Leds.LEFT, Leds.GREEN,i/255) if btn.check_buttons(buttons=['backspace']) or tr == 0: break for i in range(0,255,5): Leds.set_color(Leds.LEFT, Leds.GREEN,i/255) if btn.check_buttons(buttons=['backspace']): tr = 0 if tr == 0: break Leds.set_color(Leds.RIGHT, Leds.ORANGE,1) for i in range(255,0,-5): Leds.set_color(Leds.RIGHT, Leds.ORANGE,i/255) Leds.set_color(Leds.LEFT, Leds.GREEN,i/255) if btn.check_buttons(buttons=['backspace']): break for i in range(0,255,5): Leds.set_color(Leds.LEFT, Leds.YELLOW,i/255) if btn.check_buttons(buttons=['backspace']): tr = 0 if tr == 0: break for i in range(255,0,-5): Leds.set_color(Leds.RIGHT, Leds.RED,(255-i)/255) Leds.set_color(Leds.LEFT, Leds.YELLOW,i/255) if btn.check_buttons(buttons=['backspace']) or tr == 0: break for i in range(0,255,5): Leds.set_color(Leds.LEFT, Leds.YELLOW,i/255) if btn.check_buttons(buttons=['backspace']): tr = 0 if tr == 0: break Leds.set_color(Leds.RIGHT, Leds.RED,1) for i in range(255,0,-5): Leds.set_color(Leds.RIGHT, Leds.RED,i/255) Leds.set_color(Leds.LEFT, Leds.YELLOW,i/255) if btn.check_buttons(buttons=['backspace']): break
3. Робот должен проехать полный круг по линии и остановиться, определив и произнеся вслух диаметр круга, который на момент старта ему неизвестен. Гироскоп использовать нельзя.
# подключаем модуль для работы с EV3 from ev3dev.ev3 import * # модуль для работы со временем from time import * # также нам потребуется pi из модуля математики from math import pi # объект для работы с кнопками на блоке btn = Button() # пара объектов для работы с моторами mA = LargeMotor('outA') mD = LargeMotor('outD') # сбрасываем состояние энкодеров # при старте программы они автоматически не сбрасываются! mA.reset() mD.reset() # пара объектов для работы с датчиками освещенности # у нас датчики от NXT 1.0, если будете использовать # датчики цвета EV3, используйте класс ColorSensor CS1 = LightSensor('in2') CS2 = LightSensor('in3') # режим работы датчиков - отраженный свет # для EV3-датчиков цвета используйте 'COL-REFLECT'CS1.mode='REFLECT' CS2.mode='REFLECT' # средняя скорость движения по линии, градусов на энкодерах в секspeed = 800 # начальные сведения о радиусе круга - он неизвестен r = 0 # параметры работы ПД-регулятора u = 0 e = 0 es = 0 Pk = 1.1 Dk = 2 # создаем функцию для ограничения скорости моторов # диапазоном -900..900 град/сек def motor(speed): if speed>900: speed = 900 elif speed<-900: speed = -900 return speed # цикл пока не нажата кнопка "назад" while not btn.backspace: # рассчитываем ошибку для ПД-регулятора e = CS1.value() - CS2.value() # рассчитываем управляющее воздействие регулятора u = e*Pk + (e-es)*Dk # сохраняем ошибку для следующей итерации es = e # подаем управляющее воздействие на моторы # используя функцию, ограничивающую скорость mA.run_forever(speed_sp=motor(speed+u)) mD.run_forever(speed_sp=motor(speed-u)) # рассчитываем радиус круга if mD.position != mA.position: r = (61*(mA.position + mD.position))/(abs(mD.position-mA.position)) # выводим на консоль текущий радиус круга print(r) # небольшая задержка для работы регулятора sleep(0.01) # если проехали полный круг - вылет из цикла if (mA.position*(pi*56/360)+mD.position*(pi*56/360))/2 > pi*(r*2) and time()>3: break
# останавливаем моторы методом торможения mA.stop(stop_action="hold") mD.stop(stop_action="hold") # произносим рассчитанный в процессе движения диаметркруга Sound.speak('Stoped').wait() Sound.speak(round(r*2)).wait()
4. Робот, имеющий в своей конструкции гироскоп, должен описать круг вокруг кегли и, вернувшись на место старта, остановиться.
# подключаем модуль для работы с EV3 from ev3dev.ev3 import * # из модуля для работы со временем нам понадобится sleep from time import sleep # подключаем модуль математики from math import * # переменные для хранение данных с энкодеров ma = 0 md = 0 # объект для работы с кнопками на блоке btn = Button() # средняя скорость робота, град/сек speed = 200 # объекты для работы с моторамиmA = LargeMotor('outA') mD = LargeMotor('outD') # объекты для работы с датчиками - инфракрасным и гироскопомIR = InfraredSensor('in4') Gyro = GyroSensor("in1") # устанавливаем режим работы гироскопа (измерение угла)Gyro.mode = 'GYRO-ANG' # создаем функцию для ограничения скорости моторов # диапазоном -900..900 град/сек def motor(speed): if speed>900: speed = 900 elif speed<-900: speed = -900 return speed
# переменные для хранения расстояния и направления на ближайшую банку MIN = 100 MINGRAD = 0 # в массиве g будем хранить данным с гироскопа g = [0 for i in range(360)] # вращаемся в поисках банки mA.run_forever(speed_sp=speed) mD.run_forever(speed_sp=-1*speed) # находим ближайшую банку while Gyro.value() < 359: GyVal = Gyro.value() g[GyVal] = IR.value()*0.7 if g[GyVal] < MIN: MIN = g[GyVal] MINGRAD = GyVal # останавливаемся mA.stop(stop_action="hold") mD.stop(stop_action="hold") Sound.beep() # расчет угла для поворота к банке боком MINGRAD = MINGRAD + 90 # разворачиваемся к банке левым бортом while abs(Gyro.value()-MINGRAD) > 1: speedL = -1*(Gyro.value()-MINGRAD)*5 speedR = (Gyro.value()-MINGRAD)*5 mA.run_forever(speed_sp=motor(speedL)) mD.run_forever(speed_sp=motor(speedR)) # останавливаемся mA.stop(stop_action="hold") mD.stop(stop_action="hold") sleep(2) # сбрасываем гироскопGyro.mode = 'GYRO-RATE' Gyro.mode = 'GYRO-ANG' sleep(1)
grad = 0 # расчет длины стороны 360-ти угольника i = 2*(MIN*6+70)*tan(radians(0.5)) # сброс энкодеров mA.reset() mD.reset() # снижаем скорость до 50 граду/сек speed = 50 # цикл - пока не проехали полный круг # или не нажата кнопка while grad > -360 and not btn.backspace: ob = 56*pi/360 # если проехали 1 сторону 360-ти угольника if ((mA.position-ma)*ob+(mD.position-md)*ob)/2 > i: # поворачиваемся на 1 градус grad = grad-1 ma = mA.position md = mD.position # пропорциональный регулятор от grad speedL = speed-(Gyro.value()-grad)*16 speedR = speed+(Gyro.value()-grad)*16 # перед подачей на моторы ограничиваем скорость ф. motor() mA.run_forever(speed_sp=motor(speedL)) mD.run_forever(speed_sp=motor(speedR))
# останавливаемся после того как проехали круг mA.stop(stop_action="hold") mD.stop(stop_action="hold")
5. Робот, двигаясь по линии круга, после прохождения полной окружности должен закрутить траекторию движения правильной спиралью к центру круга. Достигнув центра круга робот должен остановиться. В программе должно быть предусмотрена возможность указания количества витков спирали, которое сделает робот до того, как остановится в центре круга.
# подключаем модуль для работы с EV3 from ev3dev.ev3 import * # нам потребуются модули математики и времени from math import * import time # объект для работы с кнопками на блоке btn = Button() # средняя скорость движения робота, град/сек speed = 300 # объекты для работы с моторами mA = LargeMotor('outA') mD = LargeMotor('outD') # объекты для работы с датчиками LS1 = LightSensor('in2') LS2 = LightSensor('in3') # режим работы датчиков освещенности - отраженный свет LS1.mode = 'REFLECT' LS2.mode = 'REFLECT' # объект для работы с гироскопом Gyro = GyroSensor("in1") # режим работы гироскопа - измерение угла Gyro.mode = "GYRO-ANG" # функция для ограничения скорости мотора (-900..900) def motor(speed): if speed>900: speed = 900 elif speed<-900: speed = -900 return speed # сброс энкодеров mA.reset() mD.reset()
# параметры ПД-регулятора e = 0 es = 0 u = 0 Pk = 3 Dk = 6 # диаметр круга роботу неизвестен D = 0 # пока не нажата кнопка назад while not btn.backspace: e = LS1.value()/10 - LS2.value()/10 u = e*Pk + (e-es)*Dk es = e mA.run_forever(speed_sp=motor(speed+u)) mD.run_forever(speed_sp=motor(speed-u)) if mA.position != mD.position: D = (61*(mA.position + mD.position))/(abs(mA.position-mD.position)) print(D)
if (mA.position*(56*pi/360)+mD.position*(56*pi/360))/2 > pi*(D*2) and time.time() > 3: break
# аналогично третьей задаче останавливаемся # после того как робот проехал полный круг
mA.stop(stop_action="hold") mD.stop(stop_action="hold") Sound.beep() # сброс гироскопа grad = 0 ma = 0 mb = 0 time.sleep(1) Gyro.mode = 'GYRO-RATE' Gyro.mode = 'GYRO-ANG' speedL = 0 speedR = 0 ma = mA.position mb = mD.position tmp = D speed = 50 # аналогично третьей задаче движемся по правильному # 360-ти угольнику, но длину сторону уменьшаем #после каждой стороны, делая "круг" меньше #как только диаметр вписанной окружности станет #менее 1 см - останавливаемся while not btn.backspace: ob = 56*pi/360 if ((mA.position-ma)*ob+(mD.position-mb)*ob)/2 > (2*D)*tan(radians(0.5)): if mA.position > mD.position: grad = grad+1 else: grad = grad-1 D = D-tmp/720 ma = mA.position mb = mD.position