无人小车位姿调试

GUI控制

模板Gui代码

#! /usr/bin/env python
# coding: utf-8

import rospy
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
from std_srvs.srv import SetBool, SetBoolRequest, SetBoolResponse
from sensor_msgs.msg import Imu, MagneticField, BatteryState
from geometry_msgs.msg import Twist
from std_msgs.msg import Float32

import threading
import time


class CtrlWindow(QWidget):

    def __init__(self):
        super(CtrlWindow, self).__init__()

        imu_subscriber = rospy.Subscriber("/zxcar/imu", Imu, self.imu_callback)
        magnetic_subscriber = rospy.Subscriber("/zxcar/mag", MagneticField, self.magnetic_callback)
        vel_subscriber = rospy.Subscriber("/zxcar/get_vel", Twist, self.vel_callback)
        butter_subscriber = rospy.Subscriber("/zxcar/battery", BatteryState, self.battery_callback)
        self.vel_publisher = rospy.Publisher("/zxcar/cmd_vel", Twist, queue_size=20)

        layout = QHBoxLayout()
        self.setLayout(layout)

        ################# 第一列 #############
        first_layout = QVBoxLayout()
        layout.addLayout(first_layout)

        ########### LED 控制
        group_led = QGroupBox("LED控制")
        first_layout.addWidget(group_led)

        led_layout = QVBoxLayout(group_led)
        btn_led_open = QPushButton("打开LED")
        btn_led_close = QPushButton("关闭LED")
        led_layout.addWidget(btn_led_open)
        led_layout.addWidget(btn_led_close)

        btn_led_open.clicked.connect(self.click_led_open)
        btn_led_close.clicked.connect(self.click_led_close)

        ########### 蜂鸣器 控制
        group_buzzer = QGroupBox("蜂鸣器控制")
        first_layout.addWidget(group_buzzer)

        buzzer_layout = QVBoxLayout(group_buzzer)
        btn_buzzer_open = QPushButton("打开蜂鸣器")
        btn_buzzer_close = QPushButton("关闭蜂鸣器")
        buzzer_layout.addWidget(btn_buzzer_open)
        buzzer_layout.addWidget(btn_buzzer_close)

        btn_buzzer_open.clicked.connect(self.click_buzzer_open)
        btn_buzzer_close.clicked.connect(self.click_buzzer_close)
        ########################################

        ################## 第2列 ##################
        second_layout = QVBoxLayout()
        layout.addLayout(second_layout)

        ################ imu陀螺仪
        group_imu = QGroupBox("imu陀螺仪")
        second_layout.addWidget(group_imu)

        imu_layout = QVBoxLayout(group_imu)
        imu_acc_layout = QHBoxLayout()
        imu_gyro_layout = QHBoxLayout()
        imu_mag_layout = QHBoxLayout()
        imu_layout.addLayout(imu_acc_layout)
        imu_layout.addLayout(imu_gyro_layout)
        imu_layout.addLayout(imu_mag_layout)
        imu_acc_layout.addWidget(QLabel("加速度"))
        imu_gyro_layout.addWidget(QLabel("线速度"))
        imu_mag_layout.addWidget(QLabel("地磁"))

        self.imu_labels = []
        for i in range(9):
            lb = QLabel("0")
            self.imu_labels.append(lb)
            if i >= 6:
                imu_mag_layout.addWidget(lb)
            elif i >= 3:
                imu_gyro_layout.addWidget(lb)
            else:
                imu_acc_layout.addWidget(lb)

        ################ 舵机
        group_servo = QGroupBox("舵机控制")
        second_layout.addWidget(group_servo)

        servo_layout = QHBoxLayout(group_servo)
        self.le_servo = QLineEdit("0")
        servo_layout.addWidget(self.le_servo)

        btn_servo = QPushButton("旋转")
        servo_layout.addWidget(btn_servo)
        btn_servo.clicked.connect(self.click_servo)

        ################### 第3列 ###################
        third_layout = QVBoxLayout()
        layout.addLayout(third_layout)

        ############### 电池信息显示
        group_battery = QGroupBox("电池信息显示")
        third_layout.addWidget(group_battery)

        battery_layout = QFormLayout(group_battery)
        self.lb_battery_voltage = QLabel("0")
        battery_layout.addRow("当前电压", self.lb_battery_voltage)

        ############### 万向轮结构速度显示
        group_vel = QGroupBox("万向轮结构速度显示")
        third_layout.addWidget(group_vel)

        vel_layout = QFormLayout(group_vel)
        self.lb_linear_vel = QLabel("0")
        vel_layout.addRow("线速度", self.lb_linear_vel)
        self.lb_angle_vel = QLabel("0")
        vel_layout.addRow("角速度", self.lb_angle_vel)

        ############### 万向轮结构速度控制
        group_vel_ctrl = QGroupBox("万向轮结构速度控制")
        third_layout.addWidget(group_vel_ctrl)

        vel_ctrl_layout = QFormLayout(group_vel_ctrl)
        self.le_linear_vel = QLineEdit("0")
        vel_ctrl_layout.addRow("线速度", self.le_linear_vel)
        self.le_angular_vel = QLineEdit("0")
        vel_ctrl_layout.addRow("角速度", self.le_angular_vel)
        self.btn_vel_ctrl = QPushButton("发送")
        vel_ctrl_layout.addRow(self.btn_vel_ctrl)

        self.le_loop_count = QLineEdit("500")
        vel_ctrl_layout.addRow("循环次数", self.le_loop_count)

        self.le_loop_delay = QLineEdit("0.1")
        vel_ctrl_layout.addRow("循环间隔(s)", self.le_loop_delay)

        self.btn_vel_loop_ctrl = QPushButton("循环发送")
        vel_ctrl_layout.addRow(self.btn_vel_loop_ctrl)

        self.btn_vel_ctrl.clicked.connect(self.click_vel_ctrl)
        self.btn_vel_loop_ctrl.clicked.connect(self.click_vel_loop_ctrl)

    def imu_callback(self, msg):
        if not isinstance(msg, Imu):
            return
        self.imu_labels[0].setText(str(msg.linear_acceleration.x))
        self.imu_labels[1].setText(str(msg.linear_acceleration.y))
        self.imu_labels[2].setText(str(msg.linear_acceleration.z))
        self.imu_labels[3].setText(str(msg.angular_velocity.x))
        self.imu_labels[4].setText(str(msg.angular_velocity.y))
        self.imu_labels[5].setText(str(msg.angular_velocity.z))

    def magnetic_callback(self, msg):
        if not isinstance(msg, MagneticField):
            return
        self.imu_labels[6].setText(str(msg.magnetic_field.x))
        self.imu_labels[7].setText(str(msg.magnetic_field.x))
        self.imu_labels[8].setText(str(msg.magnetic_field.x))

    def vel_callback(self, msg):
        if not isinstance(msg, Twist):
            return
        self.lb_linear_vel.setText(str(msg.linear.x))
        self.lb_angle_vel.setText(str(msg.angular.x))

    def battery_callback(self, msg):
        if not isinstance(msg, BatteryState):
            return
        self.lb_battery_voltage.setText(str(msg.voltage))

    def click_led_open(self):
        client = rospy.ServiceProxy("/zxcar/led", SetBool)
        client.wait_for_service()

        request = SetBoolRequest()
        request.data = True
        client.call(request)

        client.close()

    def click_led_close(self):
        client = rospy.ServiceProxy("/zxcar/led", SetBool)
        client.wait_for_service()

        request = SetBoolRequest()
        request.data = False
        client.call(request)

        client.close()

    def click_buzzer_open(self):
        client = rospy.ServiceProxy("/zxcar/buzzer", SetBool)
        client.wait_for_service()

        request = SetBoolRequest()
        request.data = True
        client.call(request)

        client.close()

    def click_buzzer_close(self):
        client = rospy.ServiceProxy("/zxcar/buzzer", SetBool)
        client.wait_for_service()

        request = SetBoolRequest()
        request.data = False
        client.call(request)

        client.close()

    def click_servo(self):
        angle = float(self.le_servo.text())
        msg = Float32()
        msg.data = angle
        self.servo_publisher.publish(msg)

    def click_vel_ctrl(self):
        linear = float(self.le_linear_vel.text())
        angular = float(self.le_angular_vel.text())
        twist = Twist()
        twist.linear.x = linear
        twist.angular.z = angular
        self.vel_publisher.publish(twist)

    def __do_loop_ctrl(self, linear, angular, loop_count, loop_delay):
        self.btn_vel_ctrl.setEnabled(False)
        self.btn_vel_loop_ctrl.setEnabled(False)

        count = 0
        while count < loop_count:
            # twist = Twist()
            # twist.linear.x = linear
            # twist.angular.z = angular
            # self.vel_publisher.publish(twist)

            count += 1
            time.sleep(loop_delay)

        self.btn_vel_ctrl.setEnabled(True)
        self.btn_vel_loop_ctrl.setEnabled(True)

    def click_vel_loop_ctrl(self):
        linear = float(self.le_linear_vel.text())
        angular = float(self.le_angular_vel.text())

        loop_count = float(self.le_loop_count.text())
        loop_delay = float(self.le_loop_delay.text())

        threading.Thread(target=self.__do_loop_ctrl, args=(linear, angular, loop_count, loop_delay)).start()


if __name__ == '__main__':
    # 创建node
    node_name = "zxcar_ctrl_node"
    rospy.init_node(node_name)

    app = QApplication(sys.argv)

    window = CtrlWindow()
    window.show()

    app.exec_()

自定义信号

class CtrlWindow(QWidget):
    _imu_signal = pyqtSignal(list)
    _magnetic_signal = pyqtSignal(list)
    _vel_signal = pyqtSignal(float, float)
    _battery_signal = pyqtSignal(float)
    _vel_loop_ctrl_signal = pyqtSignal(bool)

    def __init__(self):
        ...
        self._imu_signal.connect(self.imu_update)
        self._magnetic_signal.connect(self.magnetic_update)
        self._vel_signal.connect(self.vel_update)
        self._battery_signal.connect(self.battery_update)
        self._vel_loop_ctrl_signal.connect(self._loop_ctrl_update)
        ...

    def imu_update(self, arr):
        self.imu_labels[0].setText(str(arr[0]))
        self.imu_labels[1].setText(str(arr[1]))
        self.imu_labels[2].setText(str(arr[2]))
        self.imu_labels[3].setText(str(arr[3]))
        self.imu_labels[4].setText(str(arr[4]))
        self.imu_labels[5].setText(str(arr[5]))

    def imu_callback(self, msg):
        if not isinstance(msg, Imu):
            return
        self._imu_signal.emit(
            [msg.linear_acceleration.x, msg.linear_acceleration.y, msg.linear_acceleration.z, msg.angular_velocity.x,
             msg.angular_velocity.y, msg.angular_velocity.z])

    def magnetic_update(self, arr):
        self.imu_labels[6].setText(str(arr[0]))
        self.imu_labels[7].setText(str(arr[1]))
        self.imu_labels[8].setText(str(arr[2]))

    def magnetic_callback(self, msg):
        if not isinstance(msg, MagneticField):
            return
        self._magnetic_signal.emit([msg.magnetic_field.x, msg.magnetic_field.y, msg.magnetic_field.z])

    def vel_update(self, linear, angular):
        self.lb_linear_vel.setText(str(linear))
        self.lb_angle_vel.setText(str(angular))

    def vel_callback(self, msg):
        if not isinstance(msg, Twist):
            return
        self._vel_signal.emit(msg.linear.x, msg.angular.z)

    def battery_update(self, voltage):
        self.lb_battery_voltage.setText(str(voltage))

    def battery_callback(self, msg):
        if not isinstance(msg, BatteryState):
            return
        self._battery_signal.emit(msg.voltage)

    def click_vel_ctrl(self):
        linear = float(self.le_linear_vel.text())
        angular = float(self.le_angular_vel.text())
        twist = Twist()
        twist.linear.x = linear
        twist.angular.z = angular
        self.vel_publisher.publish(twist)

    def _loop_ctrl_update(self, enable):
        self.btn_vel_ctrl.setEnabled(enable)
        self.btn_vel_loop_ctrl.setEnabled(enable)

    def __do_loop_ctrl(self, linear, angular, loop_count, loop_delay):
        self._vel_loop_ctrl_signal.emit(False)

        count = 0
        while count < loop_count:
            twist = Twist()
            twist.linear.x = linear
            twist.angular.z = angular
            self.vel_publisher.publish(twist)

            count += 1
            time.sleep(loop_delay)

        self._vel_loop_ctrl_signal.emit(True)

里程计

分析

距离 = 速度 * 时间

计算

# vel
v_x = msg.linear.x
v_y = msg.linear.y
v_theta = msg.angular.z

# time
current_time = rospy.Time().now()

dt = current_time.to_sec() - last_time.to_sec()
delta_x = (v_x * cos(theta) - v_y * sin(theta)) * dt
delta_y = (v_x * sin(theta) + v_y * cos(theta)) * dt
delta_theta = v_theta * dt

x += delta_x
y += delta_y
theta += delta_theta

Note

线速度和角速度都是瞬时的,我们按照时间差,可以计算出在这个瞬时时间内移动的距离和转动的角度,然后将所有的数据进行累加,就可以得到小车移动的实际距离。

当前小车线速度方向只存在于x轴方向,但是考虑到以后要兼容,麦克纳姆轮结构,速度可能存在于y轴,所以计算的时候加上了y轴方向的速度。

坐标关系发布

translation = (x, y, 0.0)
rotation = quaternion_from_euler(0, 0, theta)
broadcaster.sendTransform(translation, rotation, current_time, 'base_link', 'odom')