AzoftБлогКак приручить робота: создаем механизм, управляемый через веб-интерфейс

Как приручить робота: создаем механизм, управляемый через веб-интерфейс

Алексей Романенко Сентябрь 26, 2013

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

Что и как делает робот?

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

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

Вам понадобятся

  • Микроконтроллер TI Stellaris Launchpad;
  • Два сервопривода (один поворотный, один на подъем руки) Hitec HS-322;
  • Сервопривод для клешни TowerPro SG90.

Процесс работы

Проект состоял из нескольких этапов:

  • Заказ микроконтроллеров и сервоприводов.
  • Сборка деталей:рука, подставка и др.
  • Написание и тестирование прошивки (Energia IDE) и серверной части (RubyOnRails + CofeeScript).
  • Тестирование и поиск ошибок.

Этап 1. Заказ микроконтроллеров и сервоприводов

Все лучше заказать заранее на ebay.com или aliexpress.com, т. к. в ближайшем магазине нужных деталей может не оказаться. В Новосибирске могу порекомендовать devicter.ru и hobbymarket.ru.

Этап 2. Сборка

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

Этап 3. Прошивка

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

Тестирование

Существует множество инструментов для того, что бы понять, где именно происходит ошибка и исправить ее. Например, для проверки вы можете использовать лог файлов приложения. RubyOnRails будет автоматически отображать сведения отладки. Можно также заносить свои собственные сообщения непосредственно в файл журнала из кода с помощью класса Ruby logger.

Серверная часть

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

1. Авторизация

При входе пользователю предлагалось пройти авторизацию:

class HomeController < ApplicationController
  before_filter :need_auth

  def index
    define_mode
  end 
end

Авторизация пользователей по доменным учетным записям (ldap): 

class UsersController < ApplicationController
  before_filter :check_request_method, :except => [ :sign_up, :logout ]
  
  def sign_up
    username = params[:username]
    password = params[:password]

    ldap_user = get_ldap_user(username, password)

    return do_respond(:wrong_credentials) if ldap_user.empty?

    user = User.get_by_names ldap_user[:first_name], ldap_user[:last_name]

    unless user.nil?
      authorize_user user
      return render_need_email if ldap_user[:email].nil? and user.email.nil?
      return do_respond(:choose_queue)
    end

    user = User.new
    user.email = ldap_user[:email] unless ldap_user[:email].nil?
    user.first_name = ldap_user[:first_name]
    user.last_name = ldap_user[:last_name]

    if user.save
      authorize_user user
      return render_need_email if user.email.nil?
      return do_respond :choose_queue
    else
      return do_respond :wrong_credentials
    end
  end

  def update_email
    if request.method == 'GET'
      return render_sign_up
    end

    if params[:email].empty?
      return render_need_email
    end

    email = params[:email]

    user = self.current_user

    return do_respond :wrong_credentials if user.nil?

    user.update_attribute('email', email)

    return do_respond :choose_queue
  end

  def logout
    session[:user] = nil

    respond_to do |f|
      f.html { redirect_to root_url }
    end
  end

  private
  def get_ldap_user(username, password)
    ldap = Net::LDAP.new(
        :host => 'example.com',
        :auth => { :method => :simple, :username => 'TEST\\' + username, :password => password }
    )

    @ldap_user = {}

    if ldap.bind

      ldap_base = "cn=users,dc=test,dc=test,dc=com"
      ldap_filter = Net::LDAP::Filter.eq( "samaccountname", username )
      ldap.search(:base => ldap_base, :filter => ldap_filter, :return_result => true ) do |entry|
        @ldap_user[:first_name] = entry[:givenname][0] unless entry[:givenname].nil? and entry[:givenname][0].nil?
        @ldap_user[:last_name] = entry[:sn][0] unless entry[:sn].nil? and entry[:sn][0].nil?
        @ldap_user[:email] = entry[:mail][0] unless entry[:mail].nil? and entry[:mail][0].nil?
      end
    end

    @ldap_user
  end

  def check_request_method
    if request.method == 'GET'
      respond_to do |f|
        f.html { redirect_to get_out_and_come_in_correctly and return }
      end
    end
  end
end

2. Доступ к управлению

Далее пишем часть кода, которая непосредственно отвечает за подачу команд руке:

require 'fileutils' class PlayController < ApplicationController
  #receive request from client (web-browser)
  def do_command
    # if current user can't play we send false response wich will be handled on client-side
    return false_json_response unless can_play?

    write_command(params)

    respond_to do |f|
      f.json { render :json => { :status => 'Ok' } } # it is not required to set anything in json block, you can pass just empty hash like this: {}
    end
  end

  # check if user can still play
  # return true or false (what to do in each of these cases is your own deal)
  def check_can_play
    respond_to do |f|
      f.json { render :json => can_play? }
    end
  end

  private
  # this method sends command to the hand
  def write_command(params)
    # defining which of serves should be moved
    serva_num = params[:serva_num]
    command = params[:command].rjust(3, '0')

    # hand is device actually; we are going to write into it like into file
    device = '/dev/ttyACM0'

    File.open(device, 'w') { |f| f.write(serva_num + command + "\n") }
  end
end

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

class ApplicationController < ActionController::Base
  protect_from_forgery

  def current_user
    return session[:user]
  end

  # check if current user signed in
  def signed_in?
    !self.current_user.nil?
  end

  # check if current user is admin
  def is_admin?
    signed_in? and current_user.role == User::ROLE_ADMIN
  end

  # check if it is time to play for current user
  def its_time?
    is_queued = false
    queues = UserQueue.all

    now = ((Time.now + Time.now.gmt_offset).utc.to_f * 1000).to_i

    queues.each do |queue|
      time_start = (queue.time_start.to_f * 1000).to_i
      time_end = (queue.time_end.to_f * 1000).to_i

      if !queue.user.nil? and queue.user.id == self.current_user.id and (time_start..time_end).include?(now)
        is_queued = true
        break
      end
    end

    is_queued
  end

  # check if current user still can play
  def can_play?
    return true if is_admin?
    return true if its_time?
    false
  end

  helper_method :current_user, :signed_in?, :is_admin?, :its_time?
end

Этап 4. Тестирование робота

Когда все материалы собраны, а код написан, можно переходить к тестированию продукта.

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

Подводя итоги

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

Комментарии

комментарии



Content created by Alexey Romanenko