본문 바로가기
Robot/ROS

[Lidar] Lidar로 거리측정 프로젝트 DAY1

by 9루트 2022. 2. 22.
rospy.signal_shutdown("Break")

오늘은 Lidar 실습을 하기 이전에에

ROS의 토픽 발행과 토픽 구독을 잘 할 수 있는지 점검하는 날이었다.

 

클래스를 이용하여 발행과 구독하기

 

 

1. 패키지 만들고 환경 설정해주기

 

하위 코드를 만들 때는 -p를 붙여주자.

initial 은 src 안에서 반드시 해주자.

 

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")

을 넣으면 자동으로 토픽 발행이 종료된다고 한다.

다음에는 이 코드를 활용해보자

'Robot > ROS' 카테고리의 다른 글

[Lidar]  (0) 2022.02.23
우분투 원격 제어  (0) 2022.02.22
[ROS] 과제2  (0) 2022.02.22
[통신] 참고  (0) 2022.02.15
[ROS] DAY2 Hello World - Turtlesim  (0) 2022.02.14