Rviz教程(八):Plugins: New Dockable Panel
来源:互联网 发布:简明python教程 中文 编辑:程序博客网 时间:2024/06/05 05:55
TeleopPanel
Overview
This tutorial shows how to write a simple Panel(仪表板) plugin for RViz.
A panel in RViz is a GUI widget(装饰物) which can be docked(使靠码头) in the main window or floating. It does not show properties in the “Displays” panel like a Display, but it could show things in the 3D scene.
A panel can be a useful place to put a bunch(群) of application-specific GUI elements(基础). You could put start and stop buttons for your robot, or other command or control inputs(投入).
RViz has a built-in(嵌入的) tool to send a goal pose(造成) to a path planner, but it does not have a native way to send velocity(速率) commands directly to a robot base controller. That is what this tutorial shows, a subclass(亚纲) of rviz::Panel which lets you send velocity commands right to your robot.
The source code for this tutorial is in the rviz_plugin_tutorials package. You can check out the source directly or (if you use Ubuntu) you can just apt-get install(安装) the pre-compiled Debian package like so:
Here is what RViz looks like with the new “Teleop” panel showing on the left:
The Plugin Code
The code for TeleopPanel is in these files: src/teleop_panel.h, src/teleop_panel.cpp, src/drive_widget.h, and src/drive_widget.cpp.
teleop_panel.h
The full text of teleop_panel(仪表板).h is here: src/teleop_panel.h
Here we declare our new subclass(亚纲) of rviz::Panel. Every panel which can be added via the Panels/Add_New_Panel menu is a subclass of rviz::Panel.
TeleopPanel will show a text-entry field to set the output(输出) topic and a 2D control area. The 2D control area is implemented(实施) by the DriveWidget class, and is described there.
This class uses Qt slots(位置) and is a subclass of QObject, so it needs the Q_OBJECT macro(巨大的).
QWidget subclass constructors(构造函数) usually take a parent widget(装饰物) parameter(参数) (which usually defaults to 0). At the same time, pluginlib::ClassLoader creates instances(实例) by calling the default constructor (with no arguments). Taking the parameter and giving a default of 0 lets the default constructor work and also lets someone using the class for something else to pass in a parent widget as they normally would with Qt.
Now we declare overrides(代理佣金) of rviz::Panel functions for saving and loading data from the config(配置) file. Here the data is the topic name.
Next come a couple of public Qt slots.
The control area, DriveWidget, sends its output to a Qt signal for ease of re-use, so here we declare a Qt slot to receive it.
In this example setTopic() does not get connected to any signal (it is called directly), but it is easy to define(定义) it as a public slot(位置) instead of a private function in case it would be useful to some other user.
Here we declare some internal(内部的) slots.
sendvel() publishes the current velocity(速率) values to a ROS topic. Internally this is connected to a timer which calls it 10 times per second.
updateTopic() reads the topic name from the QLineEdit and calls setTopic() with the result.
Then we finish up with protected member variables(变量).
The control-area widget(装饰物) which turns mouse events into command velocities.
One-line text editor for entering the outgoing(支出) ROS topic name.
The current name of the output(输出) topic.
The ROS publisher for the command velocity.
The ROS node handle.
The latest velocity values from the drive widget.
teleop_panel.cpp
The full text of teleop_panel(仪表板).cpp is here: src/teleop_panel.cpp
Here is the implementation(实现) of the TeleopPanel class. TeleopPanel has these responsibilities(责任):
- Act as a container for GUI elements(元素) DriveWidget and QLineEdit.
- Publish command velocities 10 times per second (whether 0 or not).
- Saving and restoring(恢复) internal state from a config(配置) file.
We start with the constructor(构造函数), doing the standard Qt thing of passing the optional(可选择的) parent argument on to the superclass(超类) constructor(构造函数), and also zero-ing the velocities(速率) we will be publishing.
Next we lay out the “output(输出) topic” text entry field using a QLabel and a QLineEdit in a QHBoxLayout.
Then create the control widget(装饰物).
Lay out the topic field above the control widget.
Create a timer for sending the output. Motor controllers want to be reassured(安心) frequently that they are doing the right thing, so we keep re-sending velocities even when they aren’t changing.
Here we take advantage of QObject’s memory management behavior(行为): since “this” is passed to the new QTimer as its parent, the QTimer is deleted by the QObject destructor(破坏者) when this TeleopPanel object is destroyed. Therefore we don’t need to keep a pointer to the timer.
Next we make signal/slot(位置) connections.
Start the timer.
Make the control widget start disabled, since we don’t start with an output topic.
setVel() is connected to the DriveWidget’s output, which is sent whenever it changes due to a mouse event. This just records the values it is given. The data doesn’t actually get sent until the next timer callback(回收).
Read the topic name from the QLineEdit and call setTopic() with the results. This is connected to QLineEdit::editingFinished() which fires when the user presses Enter or Tab or otherwise moves focus away.
Set the topic name we are publishing to.
Only take action if the name has changed.
If the topic is the empty string, don’t publish anything.
The old velocity_publisher_ is destroyed by this assignment(分配), and thus the old topic advertisement is removed. The call to nh_advertise() says we want to publish data on the new topic name.
rviz::Panel(仪表板) defines(定义) the configChanged() signal. Emitting(发出) it tells RViz that something in this panel has changed that will affect a saved config(配置) file. Ultimately(最终的) this signal can cause QWidget::setWindowModified(true) to be called on the top-level rviz::VisualizationFrame, which causes a little asterisk(星号) (“*”) to show in the window’s title bar indicating(表明)unsaved changes.
Gray out the control widget when the output topic is empty.
Publish the control velocities(速率) if ROS is not shutting down and the publisher is ready with a valid topic name.
Save all configuration(配置) data from this panel(嵌镶板) to the given Config object. It is important here that you call save() on the parent class so the class id and panel name get saved.
Load all configuration data for this panel from the given Config object.
Tell pluginlib about this class. Every class which should be loadable(适于承载的) by pluginlib::ClassLoader must have these two lines compiled(编译) in its .cpp file, outside of any namespace scope(范围).
drive_widget.h
The full text of drive_widget(装饰物).h is here: src/drive_widget.h
DriveWidget implements(工具) a control which translates mouse Y values into linear(线的) velocities and mouse X values into angular(有角的) velocities.
For maximum reusability(可重用性), this class is only responsible(负责的) for user interaction(相互作用) and display inside its widget. It does not make any ROS or RViz calls. It communicates its data to the outside just via Qt signals.
This class is not instantiated(例示) by pluginlib::ClassLoader, so the constructor(构造函数) has no restrictions(限制).
We override(推翻) QWidget::paintEvent() to do custom painting.
We override the mouse events and leaveEvent() to keep track of what the mouse is doing.
Override sizeHint() to give the layout(布局) managers some idea of a good size for this.
We emit(发出) outputVelocity() whenever it changes.
mouseMoveEvent() and mousePressEvent() need the same math to figure the velocities, so I put that in here.
A function to emit zero velocity.
Finally the member variables(变量):
drive_widget.cpp
The full text of drive_widget(装饰物).cpp is here: src/drive_widget.cpp
The DriveWidget constructor(构造函数) does the normal Qt thing of passing the parent widget to the superclass(超类) constructor, then initializing(初始化) the member variables(变量).
This paintEvent() is complex(复杂的) because of the drawing of the two arc-arrows representing wheel motion(动作). It is not particularly relevant(有关的) to learning how to make an RViz plugin(插件), so I will kind of skim(略读) it.
The background color and crosshair(十字) lines are drawn differently depending on whether this widget is enabled or not. This gives a nice visual indication(指示) of whether the control is “live”.
The main visual is a square, centered in the widget’s area. Here we compute the size of the square and the horizontal(水平的) and vertical(垂直的) offsets(抵消交易) of it.
Draw the background square.
Draw a cross-hair inside the square.
If the widget is enabled and the velocities(速率) are not zero, draw some sweet green arrows showing possible paths that the wheels of a diff-drive robot would take if it stayed at these velocities.
This code steps along a central arc defined(定义) by the linear(线的) and angular(有角的) velocites. At each step, it computes where the left and right wheels would be and collects the resulting points in the left_track and right_track arrays(数组).
Now the track arrays are filled, so stroke(抚摸) each with a fat green line.
Here we decide which direction each arrowhead(箭头) will point (forward or backward). This works by comparing the arc length travelled(有过…旅行的) by the center in one step (step_dist) with the arc length travelled by the wheel (half_track_width * delta(三角洲)_angle).
Use MiterJoin for the arrowheads so we get a nice sharp point.
Compute and draw polylines(多线) for each arrowhead. This code could probably be more elegant(高雅的).
Every mouse move event received here sends a velocity because Qt only sends us mouse move events if there was previously a mouse-press event while in the widget.
Mouse-press events should send the velocities too, of course.
When the mouse leaves the widget but the button is still held down, we don’t get the leaveEvent() because the mouse is “grabbed” (by default from Qt). However, when the mouse drags out of the widget and then other buttons are pressed (or possibly other window-manager things happen), we will get a leaveEvent() but not a mouseReleaseEvent(). Without catching this event you can have a robot stuck “on” without the user controlling it.
The ordinary way to stop: let go of the mouse button.
Compute and emit(发出) linear and angular velocities based on Y and X mouse positions relative to the central square.
update() is a QWidget function which schedules(时间表) this widget to be repainted(重画) the next time through the main event loop(环). We need this because the velocities have just changed, so the arrows need to be redrawn to match.
How to stop: emit(发出) velocities(速率) of 0!
Building the Plugin
To build the plugin(插件), just do the normal “rosmake” thing:
Exporting the Plugin
For the plugin to be found and understood by other ROS packages (in this case, rviz), it needs a “plugin_description.xml” file. This file can be named anything you like, as it is specified(指定) in the plugin package’s “manifest(证明).xml” file like so:
The contents of plugin_description.xml then look like this:
The first line says that the compiled(编译的) library lives in lib/librviz_plugin_tutorials(个别指导) (the ”.so” ending is appended(附加) by pluginlib according to the OS). This path is relative to the top directory of the package:
The next section is a class entry describing the TeleopPanel:
This specifies the name, type, base class, and description of the class. The name field must be a combination(结合) of the first two strings given to the PLUGINLIB_DECLARE_CLASS() macro(巨大的) in the source file. It must be the “package” name, a “/” slash(削减), then the “display name” for the class. The “display name” is the name used for the class in the user interface(界面).
The type entry must be the fully-qualified class name, including any namespace(s) it is inside.
The base_class_type is usually one of rviz::Panel, rviz::Display, rviz::Tool, or rviz::ViewController.
The description subsection(分段) is a simple text description of the class, which is shown in the class-chooser dialog and in the Displays panel(仪表板) help area. This section can contain HTML, including hyperlinks(超链接), but the markup(涨价) must be escaped to avoid being interpreted(说明) as XML markup. For example a link tag might look like: <a href="my-web-page.html">.
Display plugins(插件) can have multiple message_type tags, which are used by RViz when you add a Display by selecting it’s topic first.
Trying It Out
Once your RViz plugin(插件) is compiled(编译) and exported(输出物资), simply run rviz normally:
and rviz will use pluginlib to find all the plugins exported to it.
Add a Teleop panel(仪表板) by opening the “Panels” menu and then “Add New Panel” within that. This should bring up a Panel class chooser dialog with “Teleop” in it (here it is “rviz_plugin_tutorials(个别指导)”):
If “Teleop” is not in your list of Panel types, look through RViz’s console(控制台) output(输出) for error messages relating to plugin loading. Some common problems are:
- not having a plugin_description.xml file,
- not exporting it in the manifest(载货单).xml file, or
- not properly referencing(引用) the library file (like librviz_plugin_tutorials.so) from plugin_description.xml.
Once you’ve added the Teleop panel to RViz, you just need to enter a topic name to publish the geometry_msgs/Twist command velocities(速率) on. Once a non-empty string has been entered in the “Output Topic” field, the control square area should light up and accept mouse events. Holding the mouse button down in the control area sends a linear(线的) velocity based on the Y position of the mouse relative to the center and an angular(有角的) velocity based on the X position of the mouse relative to the center.
Next Steps
This Teleop panel might be useful as it is, since it already sends out command velocities appropriate(适当的) for a diff-drive robot. However, there are a few things which might make it more useful:
- Adjustable(可调节的) scaling(缩放比例) of the linear and angular velocities.
- Enforced(实施) maxima(最大数) for the velocities.
- An adjustable robot width parameter(参数), so that the curved(弯曲的) arrows accurately(精确地) show the arc a robot would traverse(穿过).
- A “strafe(猛烈炮轰)” mode (maybe when holding down the Shift key) for robots like the PR2 with (more) holonomic drive ability.
- Rviz教程(八):Plugins: New Dockable Panel
- Rviz教程(七):Plugins: New Display Type
- Rviz教程(九):Plugins: New Tool Type
- Rviz教程(十一):Rviz in Stereo
- Rviz教程(一):用户指南
- Rviz教程(十):Librviz: Incorporating RViz into a Custom GUI
- Rviz教程-Marker:发送基本的形状(C++)
- Rviz教程-Marker:点和线(C++)
- Rviz教程(二):Markers: Sending Basic Shapes (C++)
- Rviz教程(三):Markers: Points and Lines (C++)
- Rviz教程(四):Interactive Markers: Getting Started
- Rviz教程(六):Interactive Markers: Basic Controls
- Rajawali 教程07插件(Plugins)
- WebPack系列教程(七):使用Plugins
- MyBatis 教程 - MyBatis插件(Plugins)开发
- (八)ROS创建点云数据并在rviz中显示
- 10+ jQuery Sliding Sidebar Panel Plugins
- maven plugins 教程
- 万人网络5218传媒美拍涨粉加粉丝直播平台粉丝增加
- 欢迎使用CSDN-markdown编辑器
- 奇异值分解(SVD) 的 几何意义
- Linux简单的http服务器:SimpleHTTPServer
- python_猜年龄游戏代码
- Rviz教程(八):Plugins: New Dockable Panel
- 图片压缩
- 自定义控件使用DrawableLeft/Right图片和文字同时居中
- leetcode: 2. Add Two Numbers
- 图片加水印
- TensorFlow脑洞人脸识别(二)
- \r\n作用
- RabbitMQ的几种典型使用场景
- 依赖注入