rospy.signal_shutdown("Break")
오늘은 Lidar 실습을 하기 이전에에
ROS의 토픽 발행과 토픽 구독을 잘 할 수 있는지 점검하는 날이었다.
클래스를 이용하여 발행과 구독하기
1. 패키지 만들고 환경 설정해주기
하위 코드를 만들 때는 -p를 붙여주자.
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws$ ls
build devel src
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws$ ^C
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws$ gedit ~/.bashrc
build: 컴파일된 파일들이 들어간다.
devel: 의존성 관련됨. setup bash파일 안에 ROS의 모든 경로가 들어가 있다. 이미 모두 연결됨
즉 워크스페이스의 모든 경로들이 저장되어 있다.
의존성 : 어떤 라이브러리가 필요하다를 나타낸다. rospy = ros + python
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws$ cd src
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws/src$ catkin_create_pkg tutorial rospy
Created file tutorial/CMakeLists.txt
Created file tutorial/package.xml
Created folder tutorial/src
Successfully created files in /home/kw-cobot/tuto_ws/src/tutorial. Please adjust the values in package.xml.
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws/src$ ls
CMakeLists.txt tutorial
workspace를 만든 이유: 각 로봇의 기능을 나눠서 패키지를 관리하고자 하는 목적으로 만들었다.
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws/src$ cd tutorial/
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws/src/tutorial$ ls
CMakeLists.txt package.xml src
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws/src/tutorial$ mkdir scripts
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws/src/tutorial$ cd scripts/
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws/src/tutorial/scripts$ cd ..
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws/src/tutorial$ subl .
2. 토픽을 발행하는 pulisher 생성하는 코드 작성
visual studio code는 터미널도 코드 작성과 동시에 사용할 수 있는 구조이다.
토픽으 이름은 "/msg"
메세지 타입은 스트링
클래스 내의 self를 사용하면 global을 쓰지 않아도 어디서든 접근 가능하다.
#! /usr/bin/env python
import rospy
from std_msgs.msg import String
class SimplePubNode:
def __init__(self):
self.simple_pub = rospy.Publisher(
"/msg",
String,
queue_size = 5)
print("init node")
def pubMsg(self):
self.simple_pub.publish("Hello world!")
def run():
rospy.init_node("simple_pub")
spn = SimplePubNode() # Instance
rate = rospy.Rate(5)
while not rospy.is_shutdown():
spn.pubMsg()
rate.sleep()
if __name__ == "__main__":
run()
rate.sleep()을 하게 되면
simple_pub 노드가 토픽을 1초에 5번 발행하게 된다.
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws/src/tutorial$ cd scripts/
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws/src/tutorial/scripts$ ls
simple_pub.py
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws/src/tutorial/scripts$ chmod +x simple_pub.py
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws/src/tutorial/scripts$ ls
simple_pub.py
일일히 파일 마다 실행 권한 주기 어려울 때
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws/src/tutorial/scripts$ chmod +x *
*을 써서 권한을 준다.
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws$ catkin_make
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws$ source devel/setup.bash
roscore를 쳐준다.
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws$ rosrun tutorial simple_pub.py
kw-cobot@kwcobot-HGDT-Series:~$ rostopic list
/msg
/rosout
kw-cobot@kwc
위 터미널과 같이 simple_pub.py가 실행되어 SImplePubNode의 인스턴스가 만들어지고
rostopic echo /msg 로 오픽의 메세지를 불러오는 순간 main 함수가 동작된다.
3. 토픽을 구독하는 subscriber 생성하는 코드 작성
callback 함수는 토픽이 있을 때만 동작하는 함수
spin() : 콜백함수를 메모리에 올리는 역할을 한다. while문 돌리는 것과 같은 기능
#! /usr/bin/env python
import rospy
from std_msgs.msg import String
class SimpleSubNode:
def __init__(self):
self.simple_pub = rospy.Subscriber(
"/msg",
String,
self.callback
)
def callback(self, _msg):
print(_msg.data)
def run():
rospy.init_node("simple_sub")
ssn = SimpleSubNode()
rospy.spin()
if __name__ == "__main__":
run()
4. 연속된 int 형 데이터 받아 sort 정렬해주기 - 실습
실습 문제
연속된 데이터 받아서 처리해보기 Node A : int 형 데이터를 input 함수로 입력 받아 publish Node B 1) Node A가 보낸 데이터를 -1이 들어오기 전까지 받기 2)-1이 들어오면 지금까지 들어온 int 데이터들을 sorting(오름, 내림 상관 무) 3) sorting한 결과를 프린트하기 |
스트링 뿐만 아니라 레이저 값, 라이더 센서 값 등을 토픽을 이용하여 출력하고 전달할 수 있으면 ROS의 80%이상은 할 수 있는 것이다.
결론적으로 완성된 코드
1. input_sub.py
#! /usr/bin/env python
import rospy
from std_msgs.msg import Int32
class SimpleSubNode:
def __init__(self):
self.simple_pub = rospy.Subscriber(
"/msg",
Int32,
self.callback
)
self.lst = []
def callback(self, _msg):
if _msg.data == -1:
self.lst.sort()
print("sorted list: {0}".format(self.lst))
self.lst = []
else:
self.lst.append(_msg.data)
print(self.lst)
def run():
rospy.init_node("simple_sub")
ssn = SimpleSubNode()
rospy.spin()
if __name__ == "__main__":
#lst = []
run()
2. input_pub.py
#! /usr/bin/env python
import rospy
from std_msgs.msg import Int32
class SimplePubNode:
def __init__(self):
self.simple_pub = rospy.Publisher(
"/msg",
Int32,
queue_size = 5)
print("write integer number : ")
def pubMsg(self):
n = input()
if n != -1:
self.simple_pub.publish(n)
return True
else:
self.simple_pub.publish(n)
return False
def run():
rospy.init_node("simple_pub")
spn = SimplePubNode() # Instance
rate = rospy.Rate(5)
state = True
while state and not rospy.is_shutdown():
state = spn.pubMsg()
rate.sleep()
if __name__ == "__main__":
run()
print("topic finish")
4. 점검: 막혔던 부분과 알게된 점
1. string 값의 출력
1) 막혔던 부분
print("sorted list : ", lst)
부분으로 출력하면
('sorted list: ', [3, 54, 64, 87])
처럼 print 문법의 형식인 (' ' , ) 가 그대로 나오게 된다.
2) 새로 알게된 점
rospy에는 str()이 내장되어 있기 때문에 string을 출력하고자 하면
print("sorted list: {0}".format(self.lst))
처럼 명명하면 된다.
{0} {1} {2} 식으로 늘리면 되고
format(self.lst, lst2, a, b ...) 식으로 값을 늘려주면 된다.
2. melodic 2 에서의 lst.clear()
1) 막혔던 부분
-1이 입력되었을 때 sort된 리스트를 다시 clear()를 이용하여 초기화하고 싶었으나 다음과 같은 에러가 나왔다.
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws$ rosrun tutorial input_sub.py
[45]
[45, 54]
[45, 54, 354]
[45, 54, 354, 6876]
('sorted list: ', [45, 54, 354, 6876])
[ERROR] [1645514219.661964]: bad callback: <bound method SimpleSubNode.callback of <__main__.SimpleSubNode instance at 0x7fb80b3ccc30>>
Traceback (most recent call last):
File "/opt/ros/melodic/lib/python2.7/dist-packages/rospy/topics.py", line 750, in _invoke_callback
cb(msg)
File "/home/kw-cobot/tuto_ws/src/tutorial/scripts/input_sub.py", line 18, in callback
lst.clear()
AttributeError: 'list' object has no attribute 'clear'
2) 대체된 부분
melodic 버전2에서는 clear()가 내장되어있지 않기 때문에 lst.clear() 대신에
lst = [ ]로 덮어쓰기로 했다.
melonic 2에서는 clear()가 내장되어있지 않기 때문에 list.clear()를 쓸 수 없다. |
3. lst = [ ] 의 실행 오류
callback 함수 안에 lst =[ ] 를 넣고 실행을 돌렸으나 다음과 같이 오류가 발생했다.
kw-cobot@kwcobot-HGDT-Series:~/tuto_ws$ rosrun tutorial input_sub.py
[ERROR] [1645514364.469363]: bad callback: <bound method SimpleSubNode.callback of <__main__.SimpleSubNode instance at 0x7fb8776efc80>>
Traceback (most recent call last):
File "/opt/ros/melodic/lib/python2.7/dist-packages/rospy/topics.py", line 750, in _invoke_callback
cb(msg)
File "/home/kw-cobot/tuto_ws/src/tutorial/scripts/input_sub.py", line 20, in callback
lst.append(_msg.data)
UnboundLocalError: local variable 'lst' referenced before assignment
도대체 bad callback은 무슨 에러이지?.. 뭐가 나쁜데
그런데 에러의 맨 마지막 줄을 잘 보면 local variable 지역변수 lst가 assignment되기 전에 reference 되었다고 한다.
아하
if __name__ == "__main__":
lst = []
run()
메인함수에 lst를 처음 선언해주었는데
또 다시 callback 함수에서 중복 선언을 해주니 문제가 생긴 것 같다.
이 문제는 선생님의 도움을 받았다.
선생님께서는 lst를 아예 클래스 필드로 선언하는 게 어떻냐고 하였다.
2) 해결: lst를 인스턴스 필드로 선언하여 전역변수처럼 쓰자
class SimpleSubNode:
def __init__(self):
self.simple_pub = rospy.Subscriber(
"/msg",
Int32,
self.callback
)
self.lst = []
def callback(self, _msg):
if _msg.data == -1:
self.lst.sort()
print("sorted list: ",self.lst)
self.lst = []
else:
self.lst.append(_msg.data)
print(self.lst)
로 lst를 클래스 생성자 안으로 넣어주고
if __name__ == "__main__":
run()
메인 함수에서 빼주니 정상 동작이 되었다.
끝~
PS: bool 변수(state) state = False 일 때 while문을 끝내고 토픽을 발행을 정지 시키는 방법도 있지만
callback 함수 끝에
rospy.signal_shutdown("Break")
을 넣으면 자동으로 토픽 발행이 종료된다고 한다.
다음에는 이 코드를 활용해보자