개인 프로젝트

2021-2+ Moveit!입문 #5 Moveit!-2

InculPark 2022. 1. 14. 20:07
반응형

+) 계속 몸이 아프니까 의욕도 없고 일정도 계속 밀린다.....

 

[Moveit! 공부5]

 

3.10

●이 부분은 이전 글에서 내가 마지막에 end_effector 부분을 추가해서 업데이트한 것과 완전히 같은 내용이었다.

 

 

의도치 않게 예습을 했다

 

 

 

 

3.8 Moveit! Client 만들어보기 / 3.9 Moveit Client를 로봇팔에 적용해보기

●지금까지 Moveit!을 다룬 것은 client로 rviz(시각 툴)을 사용한 방법이었다. 하지만 다른 Moveit! 꼭 rviz의 시각 툴로만 사용 가능한 게 아니다. Rviz로 했던 기능을 그대로 C++이나 Python 등으로 만든 노드로 똑같이 수행할 수 있다. 어쩌면 rviz보다 이쪽이 훨씬 많이 쓰이고 어려울 것 같다.

 

 

●이 블로그 강의의 노드는 python기반이다.

 

●ros_arm_moveit_client라는 rospy, moveit_commander 의존성을 가진 새로운 패키지에서 작성한다.

 

 

●추가로 파이썬 코드를 ros(우분투) 상위 버전 (나는 noetic이었다.) 때 에러가 날 수 있는데 이때 코드 상단의 python 선언 부분을 python3로 바꿔주면 된다.

#!/usr/bin/env python  --------------------------> #!/usr/bin/env python3

 

 

●파이썬으로 노드를 만들면 패키지 빌드를 안 해도 된다는 장점이 있지만 해당 .py 파일에 실행 권한을 주어야 한다.

$chmod +x 파일명.py

 

 

 

 

아래는 내가 코드를 읽고 복붙, 수정하면서 배우거나 참고할 점들을 적어둔 것이다. (전체 코드는 강의 블로그에서 확인하자)

 

https://www.notion.so/byeongkyu/ROS-73000a9914684ac89ed8a835c6963b5e

 

ROS 로봇 프로그래밍

ROS1 (Robot Operating System)에 대한 전반적인 이해 및 다양한 도구 활용 방법 습득 실제 로봇 모델링 및 시뮬레이션 Navigation, MoveIt! 등 로봇 제어를 위한 프레임웍 실습

www.notion.so

 

 

d1_get_basic_info.py

rospy.init_node('d1_get_basic_info', anonymous=False)

●해당 파일을 대상으로 rospy기반의 노드를 실행시킨다. 이때 노드명과 코드 파일의 이름은 같아야 한다.

 

●이후 작성하는 모든 노드 코드들도 마찬가지이다.

 

robot = moveit_commander.RobotCommander()
scene = moveit_commander.PlanningSceneInterface()

●moveit_commander라는 인스턴스에서 RobotCommander()와 PlanningScenceInterface를 가져온다

 

+) 요즘 파이썬 중급자 코스로 딱 클래스 심화과정을 배우고 있다. 아직 익숙지 않아서 그런데 저 RobotCommander(), PlanningScenceInterface는 메서드인 건가?? 

 

 

$rosrun ros_arm_moveit_client d1_get_basic_info.py

d1_get_basic_info.py 실행

●실행하니 사진과 같이 현재 gazebo에 있는 ros_arm의 정보와 상태를 가져왔다.

 

+) 실행 시 gazebo로 ros_arm을 gazebo로 실행시키고 moveit 패키지의 move_group.launch도 실행시켜야 한다.

 

 

 

 

 

 

d2_move_joint_space.py

if __name__ == "__main__":

●이 부분은 python의 기초 부분에서 다뤄야 할 거 같은데 일단 배웠으니 바로 여기에 정리했다.

 

https://dojang.io/mod/page/view.php?id=2448 

 

파이썬 코딩 도장: 45.2 모듈과 시작점 알아보기

인터넷에 있는 파이썬 코드를 보다 보면 if __name__ == '__main__':으로 시작하는 부분을 자주 만나게 됩니다. if __name__ == '__main__':     코드 도대체 이 코드는 왜 사용하는 것일까요? 이 코드는 현재

dojang.io

 

●정리하면 모듈, 여기선 moveit_commander를 import 하게 되면 해당 노드가 한번 실행되는데 이때 코드가 실행되는 것(준비과정을 끝마치기 전에 실행)을 방지하기 위해 __name__변수에 무엇이 들어가 있나로 한번 조건을 걸어주는 것이다.

 

 

만약 import에 의해 실행된 것이면 __name__에는 moveit_commander가 들어가 있을 것이고(추측), 이후 import가 끝난 후 그 코드가 시작하면 __name__에는 __main__이 들어가있을 것이다.

 

 

이를 통해 코드의 시작점을 import가 아닌 본 코드가 시작할 때로 만드는 것이다.

 

 

제대로 이해한 건지는 모르겠지만 위의 블로그에서 자세히 설명하신 거 같으니 나중에 더 자세히 다룰 일이 있으면 저기를 참고해야겠다.

 

 

joint_goal[0] = 0
joint_goal[1] = -pi/3
joint_goal[2] = 2*pi/3
joint_goal[3] = 0

move_group.go(joint_goal, wait=True)
move_group.stop()

●이 6줄이 joint를 각도로 움직이는 덩어리로 조인트의 각도를 list로 넣어준 다음에 .go 메소드로 움직인다.

 

+)일단은 내가 파이썬 배운 수준에서는 move_group이라는 인스턴스가 go, stop이라는 메소드 상속받은 형태인 거 같은데 잘은

    모르겠다. import한 moveit_commander를 한번 보려고 했는데 찾지 못했다.... 

 

 

$rosrun ros_arm_moveit_client d2_move_joint_space.py

●joint_goal=[0, -pi/3, pi/3, pi/3] 으로 한 후 실행시킨 경우로 잘 작동한다.

 

move_group.launch를 실행시킨 터미널

 

 

●만약 각도를 urdf파일 joint의 <limit> tag에서 정한 영역 밖으로 주면 d2를 실행시킨 터미널에 다음의 에러가 뜬다.

Is the target within bounds?

 

●만약 불가능한 각도, 즉 link끼리 충돌을 일으키는(world와는 X)각도를 주면 에러가 move_group.launch 터미널과 d2 터미널에 각각 뜬다. d2 노드 입장에서는 각도를 성공적으로 보냈지만 moveit에서 해당 각도로 planning을 실패했다는 정보를 받고, moveit입장에서는 받은 각도로 planning을 하니 solution이 없다는 것이다. 

d2를 실행시킨 터미널 : No motion plan found

 

move_group을 실행시킨 터미널 : 0 goal, No solution found

 

 

 

 

 

 

★d3_move_target_pose.py

●end_effector의 위치와 rpy를 넣으면 그에 맞게 역기구학 계산을 해서 움직이게 하는 노드라고 한다.

1. pose_goal 이라는 변수를 geometry_msgs.Pose() 형태로 생성
2. geometry_msgs.Pose() 메시지는 Point형태의 position과 Quaternion 형태의 orientation으로 구성
3. position과 orientation 값을 입력
4. set_pose_target 메소드를 이용해 pose_goal을 전달

http://docs.ros.org/en/noetic/api/geometry_msgs/html/msg/Pose.html

 

geometry_msgs/Pose Documentation

File: geometry_msgs/Pose.msg Raw Message Definition # A representation of pose in free space, composed of position and orientation.  Point position Quaternion orientation Compact Message Definition

docs.ros.org

얕은 지식으로 코드를 분석해보았다.

 

 

다만 이 노드는 실행은 되는데 계속 moveit에서 solution을 찾지 못했다.

왜 solution을 못 찾는지 wiki랑 코드를 계속 바꿔가면서 연구했다.(이거만 또 이틀 정도 했다.)

 

 

이틀간 연구하면서 내가 내린 결론은 로봇의 자유도가 (end_effector의 y축 이동과 rpy회전)이 없어 대충 값을 입력해도 solution을 찾을 수 없다는 것이었다.

 

 

원인을 찾는 과정은 다음과 같이 진행했다.

 

 

①. d2 노드로 joint의 각도를 조절해 2개 상태를 만든 후 각 상태의 pose를 직접 만든 노드를 통해 얻는다.

상태 1&amp;amp;amp;nbsp;
상태 2

 

각각의 상태의 end_effector의 pose(position+quaternion)을 얻기 위해 아래의 코드로 노드를 하나 만들었다. 

(아래 코드는 강의 블로그의 d3_move_target_pose.py 코드를 베이스로 일부 변형해서 만든 것이다.)

 

#!/usr/bin/env python3

import sys
import copy
import rospy
import moveit_commander
import geometry_msgs.msg
from math import pi
from tf.transformations import *

 

if __name__ == "__main__":
# Initialize moveit_commander and node
moveit_commander.roscpp_initialize(sys.argv)
rospy.init_node('d3-2_get_pose', anonymous=False)

 

# Get instance from moveit_commander
robot = moveit_commander.RobotCommander()
scene = moveit_commander.PlanningSceneInterface()

 

# Get group_commander from MoveGroupCommander
group_name = "arm"
move_group = moveit_commander.MoveGroupCommander(group_name)

 

 
 
pose_goal = geometry_msgs.msg.Pose()
 
# get_end_effector_link() 메서드로 end_effector의 link이름을 가져옴
eef_name = move_group.get_end_effector_link()    
# get_current_pose() 메소드로 현재 ros_arm 자세에서 end_effector의 pose를 가져옴
pose = move_group.get_current_pose(eef_name)  
 

# end_effector의 link이름과 pose를 print함

print('End effector link : {}'.format(eef_name))
print()
print(pose)
 

quit()

 

이 과정에서 moveit client 노드를 만들 때 사용할 수 있는 메소드의 목록과 사용법에 대한 문서를 참고했다. (엄청 유용하다)

 

http://docs.ros.org/en/jade/api/moveit_commander/html/classmoveit__commander_1_1move__group_1_1MoveGroupCommander.html#a8428de2978fc954bdd645947ee23f566

 

moveit_commander: moveit_commander.move_group.MoveGroupCommander Class Reference

Execution of simple commands for a particular group Definition at line 44 of file move_group.py.

docs.ros.org

 

 

 

 

②. d3 노드에 위에서 얻은 pose값을 넣는다.

 

이 경우 나는 d3노드를 이용해 상태 1에서 상태 2(위 gazebo 캡처 사진 2장)로 만드는 것을 목표로 했다.

직접 만든 d3-2노드로 얻은 상태 2의 pose

 

한편 강의에서 예시로 준 d3 노드의 코드에는 quat를 quaternion_from_euler 함수로 rpy를 quaternion으로 바꿔서 넣었는데 d3-2 노드로 얻은 정보의 orientation은 이미 quaternion이므로 함수 없이 그냥 그 값을 그대로 넣었다.

일부 수정한 강의의 d3 노드 코드

 

이런 과정을 거쳐서 하니 그제야 gazebo의 ros_arm이 움직였다.

 

최우측 3개 터미널은 위에서 부터 gazebo, move_group, d3 노드

 

 

이때 d2노드를 이용해 같은 자세를 만들어도 매번 pose 값이 조금씩 달랐는데 d3 노드에 값을 넣을 때 position과 oreintation이 같은 trial에서 나온 게 아니면 No solution이 나왔다.★

 

= 같은 동작이라도 같은 trial에서 나온 각도 값이 아니라면 No solution이 나왔다.

 

 

 

 

 

 

d4_move_cartesian_path.py

●얘는 따지고 보면 d3의 방법과 같은 방법이다. d3가 position과 quaternion으로 end_effector의 위치와 방향을 지정해 움직이게 했다면 d4는 d3로 이동 후의 end_effector의 position에 그냥 내가 원하는 이동을 cartesian coordinate으로 더해서 새로운 position을 주는 방법이다.

 

# Move using cartesian path
waypoints = []

wpose = pose_goal
 
wpose.position.x += 0.03
wpose.position.z -= 0.03
waypoints.append(copy.deepcopy(wpose))     
 #경유점을 list 형태로 넣는 것이기에 여러 waypoints를 append하면 그 모든 점을 경유하면서 움직인다.
 

(plan, fraction) = move_group.compute_cartesian_path(waypoints, 0.01, 0.0)

 

코드의 이 부분 위쪽까지는 d3의 코드와 완전히 똑같다.

 

 

이 부분을 보면 pose_goal, 즉 d3로 움직인 후의 end_effector의 pose정보를 wpose에 넣고 단순히 position 값을 더하고 빼서 움직이게 한다. 

 

 

다만 이 때는 planning에

move_group.set_pose_target(pose_goal)을 사용하지 않고

(plan, fraction) = move_group.compute_cartesian_path(waypoints, 0.01, 0.0)을 사용했다.

 

 

또한 경유점은 위의 

wpose.position.x ~ waypoints.append 부분 까지를 이어서 써주면 된다.

 

ros_arm을 움직이게 하는 경유점들
위의 경유점을 움직이는 ros_arm (d4_move_cartesian_path.py)

 

●한 가지 이상한 점은 최초 상태에서 x축 방향으로 -이동을 하려고 하면 에러가 났다. 도저히 불가능한 자세가 아닌데 계속 invailid goal이라고 에러가 났다. 이 부분은 나중에 원인을 알아봐야겠다.

 

 

 

d5-1_add_box.py, d5-4_remove_box.py

 

 

 

 

 

 

 

d5-2_attach_box.py, d5-3_dettach_box.py

 

 

 

 

 

 

 

========================================================================

정확한 정보 전달보단 공부 겸 기록에 초점을 둔 글입니다.

틀린 내용이 있을 수 있습니다.

틀린 내용이나 다른 문제가 있으면 댓글에 남겨주시면 감사하겠습니다.   : )

========================================================================

반응형