Usando o LeapMotion para controlar um robô meArm I

Abaixo, o vídeo dos meus primeiros resultados controlando o meArm a partir do sensor LeapMotion.


Esse sensor é semelhante ao Kinect, ou seja, detecta movimentos do nosso corpo (no caso, somente as mãos e braços) e constrói, usando modelos matemáticos bastante complexos, um modelo 3D que descreve cada parte do que está em seu "campo de visão". Essa detecção é feita com emissão e captação de radiação infravermelha.

Como a matemática por trás do troço é muito complexa, ele precisa do PC, ou seja, ainda não é possível ter uma plaquinha das que a gente usa (Arduino, RPi, Edison etc) para processar diretamente os sinais enviados pelo sensor.

Dito isso, se quisermos comandar algum device com o LM, devemos estabelecer a comunicação entre o PC onde o LM está conectado e o dispositivo a ser comandado.

No caso, a ideia é estabelecer uma maneira de comandar o meArm (pequeno robô visto no vídeo acima) de forma total, ou seja, todos os movimentos, a partir de movimentos captados pelo LM em uma ou duas mãos.

Como o robô pode ser controlado com um Arduino, pensei em ligar o Arduino no PC, captar os movimentos do meArm com uma aplicação em Python, processar esses comandos para facilitar o comando pelo Arduino e enviar pro Arduino via porta serial.

Os primeiros testes, me pareceu óbvio, seriam mais fáceis com a pinça, ou seja, fechando e abrindo a pinça do robô com movimentos em pinça do polegar contra o indicador.

Esse post vai tratar de como isso foi feito.

Primeiro, vamos analisar a aplicação Python:

###############################################################################
#                                                                             #
# This program tests the tweezers movement in meArm controlled by Leap Motion #
# You can see more in http://automatobr.blogspot.com.br                       #
#                                                                             #
# assismauro@hotmail.com                                                      #
#                                                                             #
###############################################################################
import sys
# Bring serial stuff
import serial
import thread, time

# Add path to LeapMotion libs directory 
sys.path.insert(0,"D:\Atrium\Projects\Arduino\LeapMotion\lib")

# import LM stuff
import Leap
from Leap import CircleGesture, KeyTapGesture, ScreenTapGesture, SwipeGesture

# initialize Arduino/PC communication. You must set COM port accordingly your Arduino connection
arduino = serial.Serial('COM45', 9600, timeout=.1)

# This listener is called anytime some data is available form LM
class SampleListener(Leap.Listener):

# some readable names to use later
    finger_names = ['Thumb', 'Index', 'Middle', 'Ring', 'Pinky']
    bone_names = ['Metacarpal', 'Proximal', 'Intermediate', 'Distal']

    def on_init(self, controller):
        print "Initialized"

    def on_connect(self, controller):
        print "Connected"

    def on_frame(self, controller):

        global arduino

        # Get the most recent frame, that contains data sent from LM engine
        frame = controller.frame()

        # Get hands
        if len(frame.hands) == 0:
            return
        
        hand = frame.hands[0] # only one hand

        # Get fingers: we only need Thumb and Index data, for a while
        for finger in hand.fingers:
            if (self.finger_names[finger.type()] == "Thumb") or (self.finger_names[finger.type()] == "Index"):
            # Get bones
                for b in range(0, 4):
                    bone = finger.bone(b)

                    d=0.0
                    i=0.0
                    tshMin = 10.0
                    tshMax = 100.0
                    # The idea is to process only distal bone data, that corresponds to te tip of our fingers.
                    # The LM coordinate system is a X,Y,Z based in the center of the sensor (see picture in my
                    # blog). As we need only the distance between tips, in a tweezers movement, I transform it
                    # in a "percent distance", that is, 0 corresponds to tweezer closed, and 100, tweezer
                    # oppened.
                    if (self.bone_names[bone.type] == "Distal"):
                        if (self.finger_names[finger.type()] == "Thumb"):
                            t=bone.next_joint[0]
                        else:
                            i=bone.next_joint[0]
                            d=abs(t-i)
                            o=100 if d >= tshMax else (0 if (d <= tshMin) else (d-tshMin)/tshMax*100.0)
                            print o
                            # here we send data to Arduino
                            toSend = str(o)+"\n"
                            arduino.write(toSend)
                            # and... that´s it.

def main():
    # Create a sample listener and controller
    listener = SampleListener()
    controller = Leap.Controller()
     
    # Have the sample listener receive events from the controller
    controller.add_listener(listener)

    # Keep this process running until Enter is pressed
    print "Press Enter to quit..."
    try:
        sys.stdin.readline()
    except KeyboardInterrupt:
        pass
    finally:
        # Remove the sample listener when done
        controller.remove_listener(listener)

if __name__ == "__main__":
    main()

O código acima, devidamente comentado, é responsável por enviar ao Arduino os comandos do motor da pinça (garra) do meArm.

Abaixo, o programa que roda no Arduino:


/*
  Program to command meArm gripper from LeapMotion (Arduino side)
  More about that: http://automatobr.blogspot.com.br
  assismauro@hotmail.com
*/

#include <Servo.h>
#include <SoftwareSerial.h>

// Debug communication, if you think is necessary
SoftwareSerial mySerial(7,8);

// meArm servo pins
int basePin = 11;
int shoulderPin = 10;
int elbowPin = 9;
int gripperPin = 6;

Servo base;
Servo shoulder;
Servo elbow;
Servo gripper;

void setup() {
  /*
  base.attach(basePin);
  shoulder.attach(shoulderPin);
  elbow.attach(elbowPin);
  */
  // Gripper test
  gripper.attach(gripperPin);
  Serial.begin(9600);
  mySerial.begin(9600);
  gripper.write(90);
  delay(500);
  gripper.write(120);
  delay(500);
  
}

// Stores the last cmd received
int cmdOld=-1;

// Threshold
int limiar = 5;

void loop() 
{
  String cmdStr = "";
  // Get command from Pyton PC app
  if(Serial.available())
     cmdStr=Serial.readStringUntil(10);
  
  if(cmdStr != "")
  {
     int cmd=cmdStr.toInt();
     //Check threshold
     if((cmd > 100) || (abs(cmd - cmdOld) < limiar))
        return;
     cmdOld=cmd;
     // Map LeapMotion command to gripper servo angle   
     int gripAngle=map(cmd,0,100,140,90);
     gripper.write(gripAngle);
     delay(30);
     // Send data to debug Arduino
     mySerial.print(cmd);
     mySerial.print("  -  ");
     mySerial.println(gripAngle);
  }
}

O programa é bem simples: recebe o comando que vem do Python e o trata, acionando o motor da garra.

Dois detalhes importantes:

1) Vc pode observar que tem a definição de uma segunda porta serial. Eu fiz isso para depurar o meu programa, porque como a porta serial que normalmente a gente usa para se comunicar com o Arduino está ocupada pelo próprio. Aí eu liguei um segundo Arduino a outro PC e conectei os dois criando uma porta serial usando os pinos 7 e 8. Aí deu para monitorar os comandos que estavam chegando enviando-os ao outro Arduino.

2) Outro pronto é a variável limiar.  Ela serve para suavizar os movimentos do meArm, porque os comandos recebidos variam muito. Isso evita a "tremedeira" da garra.

É isso.


Comentários

Postagens mais visitadas deste blog

Controle PID de Potência em Corrente Alternada - Arduino e TRIAC - Parte III

Dividindo um programa (sketch) do Arduino em mais de um arquivo

Controle PID de Potência em Corrente Alternada - Arduino e TRIAC - Parte I