์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ

21. [ROS RC์นด] ROS ์—ฐ๋™ RC์นด ์ฃผํ–‰ 2

Swimming_Kim 2021. 1. 29. 23:28
๐ŸŽฎ

21. [ROS RC์นด] ROS ์—ฐ๋™ RC์นด ์ฃผํ–‰ 2

์ง€๋‚œ ์‹œ๊ฐ„์— ์ด์–ด์„œ ์ฝ”๋“œ ์„ค๋ช…์„ ๊ณ„์†ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
  • donkey_control :

    subscribe : ackermann_msgs/AckermannDriveStamped

    โ‡’ PWM (PCA9685)

๋๋‹จ ๋…ธ๋“œ์—์„œ๋ถ€ํ„ฐ ์—ญ์ˆœ์œผ๋กœ ์˜ฌ๋ผ์˜ค๋ฉด์„œ ์‚ดํŽด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

donkey_control


  • joy_control.py

์ฝ”๋“œ๋Š” ๋‹ค์Œ ์œ„์น˜์— ์žˆ์Šต๋‹ˆ๋‹ค. โ‡’ donkey_ros/donkey_control/src

#!/usr/bin/env python

"""
Node for control PCA9685 using AckermannDriveStamped msg 
referenced from donekycar
url : https://github.com/autorope/donkeycar/blob/dev/donkeycar/parts/actuator.py
"""

import time
import rospy
from threading import Thread
from ackermann_msgs.msg import AckermannDriveStamped


class PCA9685:
    """
    PWM motor controler using PCA9685 boards.
    This is used for most RC Cars
    """

    def __init__(
        self, channel, address=0x40, frequency=60, busnum=None, init_delay=0.1
    ):

        self.default_freq = 60
        self.pwm_scale = frequency / self.default_freq

        import Adafruit_PCA9685

        # Initialise the PCA9685 using the default address (0x40).
        if busnum is not None:
            from Adafruit_GPIO import I2C

            # replace the get_bus function with our own
            def get_bus():
                return busnum

            I2C.get_default_bus = get_bus
        self.pwm = Adafruit_PCA9685.PCA9685(address=address)
        self.pwm.set_pwm_freq(frequency)
        self.channel = channel
        time.sleep(init_delay)  # "Tamiya TBLE-02" makes a little leap otherwise

        self.pulse = 340
        self.prev_pulse = 340
        self.running = True

    def set_pwm(self, pulse):
        try:
            self.pwm.set_pwm(self.channel, 0, int(pulse * self.pwm_scale))
        except:
            self.pwm.set_pwm(self.channel, 0, int(pulse * self.pwm_scale))

    def run(self, pulse):
        pulse_diff = pulse - self.prev_pulse

        if abs(pulse_diff) > 40:
            if pulse_diff > 0:
                pulse += 0.7 * pulse_diff
            else:
                pulse -= 0.7 * pulse_diff

        self.set_pwm(pulse)
        self.prev_pulse = pulse

    def set_pulse(self, pulse):
        self.pulse = pulse

    def update(self):
        while self.running:
            self.set_pulse(self.pulse)


class Vehicle(object):
    def __init__(self, name="donkey_ros"):

        self._throttle = PCA9685(channel=0, busnum=1)
        rospy.loginfo("Throttle Controller Awaked!!")

        self._steering_servo = PCA9685(channel=1, busnum=1)
        rospy.loginfo("Steering Controller Awaked!!")

        self._name = name
        self._teleop_sub = rospy.Subscriber(
            "/donkey_teleop",
            AckermannDriveStamped,
            self.joy_callback,
            queue_size=1,
            buff_size=2 ** 24,
        )
        rospy.loginfo("Teleop Subscriber Awaked!! Waiting for joystick...")

    def joy_callback(self, msg):
        speed_pulse = msg.drive.speed
        steering_pulse = msg.drive.steering_angle

        print(
            "speed_pulse : "
            + str(speed_pulse)
            + " / "
            + "steering_pulse : "
            + str(steering_pulse)
        )

        self._throttle.run(speed_pulse)
        self._steering_servo.run(steering_pulse)


if __name__ == "__main__":

    rospy.init_node("donkey_control")
    myCar = Vehicle("donkey_ros")

    rate = rospy.Rate(10)
    while not rospy.is_shutdown():
        rate.sleep()

ํ•ญ์ƒ ๊ทธ๋žฌ๋˜ ๊ฒƒ๊ณผ ๊ฐ™์ด ์•ž์—์„œ๋ถ€ํ„ฐ ์ฐจ๊ทผ์ฐจ๊ทผ ์‚ดํŽด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž๐Ÿ˜Ž

  • import

์•ž์„œ ์‚ดํŽด๋ณธ ackermann_msgs์˜ AckermannDriveStamped๊ฐ€ ๋ณด์ž…๋‹ˆ๋‹ค.

import time
import rospy
from threading import Thread
from ackermann_msgs.msg import AckermannDriveStamped

์–ด๋– ํ•œ ๋ชจ์Šต์ด์—ˆ๋Š”์ง€ ๋‹ค์‹œ ํ•œ ๋ฒˆ ์งš๊ณ  ๋„˜์–ด๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค.

ackermann_msgs/AckermannDriveStamped Documentation
http://docs.ros.org/en/jade/api/ackermann_msgs/html/msg/AckermannDriveStamped.html
$ rosmsg info ackermann_msgs/AckermannDriveStamped 
std_msgs/Header header
  uint32 seq
  time stamp
  string frame_id
ackermann_msgs/AckermannDrive drive
  float32 steering_angle
  float32 steering_angle_velocity
  float32 speed
  float32 acceleration
  float32 jerk

  • main
if __name__ == "__main__":

    rospy.init_node("donkey_control")
    myCar = Vehicle("donkey_ros")

    rate = rospy.Rate(10)
    while not rospy.is_shutdown():
        rate.sleep()

Vehicle์ด๋ผ๋Š” ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ , 10Hz์˜ ์ฃผ๊ธฐ๋ฅผ ๊ฐ–๊ณ  ๋ฐ˜๋ณต์‹œํ‚ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

  • Vehicle Class

์‹ค์งˆ์ ์ธ subscriber์ž…๋‹ˆ๋‹ค. PCA9685 ํด๋ž˜์Šค 2๊ฐœ๋ฅผ ์„ ์ •ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์œ ์˜ํ•˜์…”์•ผ ํ•  ์ ์œผ๋กœ, ๋ฒ„๋ฒ…์ž„์„ ์ค„์ด๊ธฐ ์œ„ํ•ด buff_size๋ฅผ ์ถ”๊ฐ€ ์„ค์ •ํ•œ ๊ฒƒ์ด ๋ณด์ž…๋‹ˆ๋‹ค. ๋กœ๊ทธ๋ฅผ ์ฐ๊ณ  ์žˆ๋Š” ๋ชจ์Šต๋„ ๋ณด์ด๋„ค์š”.

class Vehicle(object):
    def __init__(self, name="donkey_ros"):

        self._throttle = PCA9685(channel=0, busnum=1)
        rospy.loginfo("Throttle Controller Awaked!!")

        self._steering_servo = PCA9685(channel=1, busnum=1)
        rospy.loginfo("Steering Controller Awaked!!")

        self._name = name
        self._teleop_sub = rospy.Subscriber(
            "/donkey_teleop",
            AckermannDriveStamped,
            self.joy_callback,
            queue_size=1,
            buff_size=2 ** 24,
        )
        rospy.loginfo("Teleop Subscriber Awaked!! Waiting for joystick...")

    def joy_callback(self, msg):
        speed_pulse = msg.drive.speed
        steering_pulse = msg.drive.steering_angle

        print(
            "speed_pulse : "
            + str(speed_pulse)
            + " / "
            + "steering_pulse : "
            + str(steering_pulse)
        )

        self._throttle.run(speed_pulse)
        self._steering_servo.run(steering_pulse)

  • PCA9685 Class

์ด class๋Š” donkeycar ํ”„๋กœ์ ํŠธ์—์„œ ๊ฐ€์ ธ์˜จ ๊ฒƒ์ž…๋‹ˆ๋‹ค. donekycar๋Š” ์˜ค๋กœ์ง€ ํŒŒ์ด์ฌ์œผ๋กœ ์งœ์—ฌ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋Œ€๋ถ€๋ถ„์˜ ์ž‘์—…๋“ค์ด threading์„ ํ†ตํ•ด ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ROS๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด, ์ด๋Ÿฌํ•œ ์ž‘์—…์€ ํฌ๊ฒŒ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š์•„๋„ ๋œ๋‹ค๋Š” ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

class PCA9685:
    """
    PWM motor controler using PCA9685 boards.
    This is used for most RC Cars
    """

    def __init__(
        self, channel, address=0x40, frequency=60, busnum=None, init_delay=0.1
    ):

        self.default_freq = 60
        self.pwm_scale = frequency / self.default_freq

        import Adafruit_PCA9685

        # Initialise the PCA9685 using the default address (0x40).
        if busnum is not None:
            from Adafruit_GPIO import I2C

            # replace the get_bus function with our own
            def get_bus():
                return busnum

            I2C.get_default_bus = get_bus
        self.pwm = Adafruit_PCA9685.PCA9685(address=address)
        self.pwm.set_pwm_freq(frequency)
        self.channel = channel
        time.sleep(init_delay)  # "Tamiya TBLE-02" makes a little leap otherwise

        self.pulse = 340
        self.prev_pulse = 340
        self.running = True

    def set_pwm(self, pulse):
        try:
            self.pwm.set_pwm(self.channel, 0, int(pulse * self.pwm_scale))
        except:
            self.pwm.set_pwm(self.channel, 0, int(pulse * self.pwm_scale))

    def run(self, pulse):
        pulse_diff = pulse - self.prev_pulse

        if abs(pulse_diff) > 40:
            if pulse_diff > 0:
                pulse += 0.7 * pulse_diff
            else:
                pulse -= 0.7 * pulse_diff

        self.set_pwm(pulse)
        self.prev_pulse = pulse

    def set_pulse(self, pulse):
        self.pulse = pulse

    def update(self):
        while self.running:
            self.set_pulse(self.pulse)

Adafruit_PCA9685 ๋ชจ๋“ˆ์„ ๊ฐ€์ ธ์˜ค๋Š” ๋ชจ์Šต์ด ๋ณด์ด๋ฉฐ, PWM Pulse๋ฅผ ์กฐ์ ˆํ•˜์—ฌ ๋ชจํ„ฐ๋ฅผ ๊ตฌ๋™์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์ด์ „ calibration ๊ฐ•์˜์—์„œ ๋ณด์•˜๋“ฏ, ๊ฐ‘์ž๊ธฐ ํฐ ์กฐํ–ฅ์„ ์ฃผ๋ฉด, ๋ฉˆ์ถฐ๋ฒ„๋ฆด ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฒ˜๋ฆฌ๊ฐ€ ๋“ค์–ด๊ฐ”์Šต๋‹ˆ๋‹ค. (maze ์˜ˆ์ œ์—์„œ ๋ณด์•˜๋˜ ๋ฐฉ์‹์ด์ง€์š”?)
        if abs(pulse_diff) > 40:
            if pulse_diff > 0:
                pulse += 0.7 * pulse_diff
            else:
                pulse -= 0.7 * pulse_diff

๋‹ค์‹œ๊ธˆ PCA9685 ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ถ„์„ ๊ฐ€์ ธ์™”์Šต๋‹ˆ๋‹ค.

throttle, steering์ด ๊ฐ๊ฐ 0๋ฒˆ 1๋ฒˆ ์ฑ„๋„์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋Š” ์  ์ƒ๊ธฐ์‹œ์ผœ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

class Vehicle(object):
    def __init__(self, name="donkey_ros"):

        self._throttle = PCA9685(channel=0, busnum=1)
        rospy.loginfo("Throttle Controller Awaked!!")

        self._steering_servo = PCA9685(channel=1, busnum=1)
        rospy.loginfo("Steering Controller Awaked!!")

joy teleop axes


์ด ๋…ธ๋“œ๋Š”, mit-racecar ๋ผ๋Š” ํ”„๋กœ์ ํŠธ์—์„œ ํ•„์š”ํ•œ ๋ถ€๋ถ„์„ ๊ฐ€์ ธ์™€ ์ˆ˜์ •ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ›Œ๋ฅญํ•œ ํ”„๋กœ์ ํŠธ์ด๋‹ˆ ์‹œ๊ฐ„์ด ๋˜์‹ค ๋•Œ ํ•จ๊ป˜ ์‚ดํŽด๋ณด์‹œ๋ฉด ์ข‹์Šต๋‹ˆ๋‹ค. ๐Ÿ‘๐Ÿ‘

Originally from https://github.com/ros-teleop/teleop_tools Pulled on April 28, 2017. Edited by Winter Guerra on April 28, 2017 to allow for default actions.

mit-racecar/racecar
Contribute to mit-racecar/racecar development by creating an account on GitHub.
https://github.com/mit-racecar/racecar

์ฝ”๋“œ๋ฅผ ๋ณผ ํ•„์š”์—†์ด, ์‚ฌ์šฉํ•˜๊ธฐ ํŽธ๋ฆฌํ•˜๊ฒŒ ์„ค์ • ๋ถ€๋ถ„์„ ๋”ฐ๋กœ yaml ํŒŒ์ผ๋กœ ๋นผ๋‘” ํ”„๋กœ์ ํŠธ์ธ๋ฐ์š”. ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์œ„์น˜์— ์žˆ์Šต๋‹ˆ๋‹ค.

  • donkey_joy/config/joy_teleop.yaml
joy_node:
  deadzone: 0.01
  autorepeat_rate: 20
  coalesce_interval: 0.01

teleop:
  # Default mode - Stop for safety
  default:
    type: topic
    is_default: true
    message_type: ackermann_msgs/AckermannDriveStamped
    topic_name: donkey_teleop
    message_value:
      -
        target: drive.speed
        value: 370.0
      -
        target: drive.steering_angle
        value: 375.0

  # Enable Human control by holding Left Bumper
  human_control:
    type: topic
    message_type: ackermann_msgs/AckermannDriveStamped
    topic_name: donkey_teleop
    deadman_buttons: [4]
    axis_mappings:
      -
        axis: 1
        target: drive.speed
        scale: 40.0                   # joystick will command plus or minus 2 meters / second
        offset: 380.0
      -
        axis: 2
        target: drive.steering_angle
        scale: 78.0                  # joystick will command plus or minus ~20 degrees steering angle
        offset: 375.0

  # Enable autonomous control by pressing right bumper
  # This switch causes the joy_teleop to stop sending messages to input/teleop
  # And send messages to /dev/null (an unused ROS topic)
  autonomous_control:
    type: topic
    message_type: std_msgs/Int8
    topic_name: /dev/null
    deadman_buttons: [5]
    message_value:
      -
        target: data
        value: 0
  • message_type : ์‚ฌ์šฉํ•˜๋Š” topic msg ํƒ€์ž…์ž…๋‹ˆ๋‹ค.
  • topic_name : publishํ•  topic ์ด๋ฆ„์ž…๋‹ˆ๋‹ค.
  • deadman_buttons : ํŠธ๋ฆฌ๊ฑฐ ๋ฒ„ํŠผ์ž…๋‹ˆ๋‹ค. (์ปจํŠธ๋กค๋Ÿฌ ๋’ค์— LB๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ์ค˜์•ผ ํ•œ๋‹ค๊ณ  ๋งํ–ˆ์—ˆ์ง€์š”?)
  • axis_mappings/offset : ์กฐ์ข… ์‹ ํ˜ธ์˜ ์˜์ ์œผ๋กœ, ์•ž์„  donkey car calibration ๋‹น์‹œ ์•Œ์•„๋‚ธ ๊ฐ’์„ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.
  • axis_mappings/scale : offset ์„ ๊ธฐ์ ์œผ๋กœ ์–ด๋Š ์ •๋„์˜ ๋ฒ”์œ„๋ฅผ ๊ฐ€์ง€๊ณ  ๋ณ€ํ™”ํ•  ๊ฒƒ์ธ์ง€ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฐ’์ž…๋‹ˆ๋‹ค.

joy node


์ด ๋…ธ๋“œ๋Š”, ros์—์„œ ๊ธฐ๋ณธ ์ œ๊ณตํ•˜๋Š” ํŒจํ‚ค์ง€์ž…๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ sudo apt-get ์œผ๋กœ ์„ค์น˜๊ฐ€ ๊ฐ€๋Šฅํ–ˆ์—ˆ์ง€์š”.

์‚ฌ์šฉํ•˜๋Š” message type์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

$ rosmsg info  sensor_msgs/Joy
std_msgs/Header header
  uint32 seq
  time stamp
  string frame_id
float32[] axes
int32[] buttons
sensor_msgs/Joy Documentation
Reports the state of a joysticks axes and buttons. Header header # timestamp in the header is the time the data is received from the joystick float32[] axes # the axes measurements from a joystick int32[] buttons # the buttons measurements from a joystick
http://docs.ros.org/en/melodic/api/sensor_msgs/html/msg/Joy.html

  • axes : ์–‘์ชฝ ์กฐ์ดํŒจ๋“œ ์ƒํ•˜์ขŒ์šฐ ๋ฒ„ํŠผ์— ๋งคํ•‘๋˜๋ฉฐ, ๋ชจ๋‘ ์ตœํ•ฉํ•˜์—ฌ ๋ฐฐ์—ด๋กœ publish๋ฉ๋‹ˆ๋‹ค.
  • buttons : X Y A B ๊ทธ๋ฆฌ๊ณ  ์ž‘์€ ํƒ€์›ํ˜• ๋ฒ„ํŠผ, ํ›„๋ฉด L/R ๋ฒ„ํŠผ๊นŒ์ง€ ๋ชจ๋‘๋ฅผ mappingํ•ด์„œ ๋ฐฐ์—ด๋กœ ์ „๋‹ฌํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

ํ„ฐ๋ฏธ๋„์„ ํ†ตํ•ด ํ™•์ธํ•ด ๋ณผ๊นŒ์š”?

# connection check!!
$ sudo jstest /dev/input/js0

$ roscore
$ rosrun joy joy_node
$ rostopic echo /joy

Wiki
This stack contains a ROS node to interface with joysticks, and drivers for joysticks that are not well supported by a common Linux distribution. This stack contains a ROS node to interface with joysticks, and drivers for joysticks that are not well supported by a common Linux distribution.
http://wiki.ros.org/joystick_drivers

Reference


ROS on Multiple Computers | Connecting Raspberry Pi with PC over LAN
In this blog I will provide instruction for setting up Network Configuration on both of your raspberry pi and PC. In my last blog i showed the way to install ROS on raspberry pi zero , so now we ca...
https://razbotics.wordpress.com/2018/01/23/ros-distributed-systems/
RACECAR/J - ROS Teleoperation - JetsonHacks
After completion of the RACECAR/J robot assembly, ROS software installation and VESC programming, it is time to test teleoperation using a game controller. Looky here: In the software installation article, we installed a custom ROS software stack for the MIT RACECAR.
https://www.jetsonhacks.com/2018/08/19/racecar-j-ros-teleoperation/
mit-racecar/racecar
Contribute to mit-racecar/racecar development by creating an account on GitHub.
https://github.com/mit-racecar/racecar

์ฒซ๋ฒˆ์งธ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งˆ์ณค์Šต๋‹ˆ๋‹ค. ์ด ์ฝ”๋“œ๋“ค์„ ์‚ฌ์šฉํ•ด์„œ ์‘์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๋“ค์ด ๋งˆ๊ตฌ๋งˆ๊ตฌ ์ƒ๊ฐ๋‚˜์ง€ ์•Š๋‚˜์š”? ๐Ÿ˜‰ ์ €๋Š” ๋‹ค์Œ ๊ฐ•์˜๋กœ ๋„˜์–ด๊ฐ€์„œ ์นด๋ฉ”๋ผ๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๐Ÿ“ธ