开发ROS相关的web应用(二) - 在web中与turtlebot仿真互动

本文介绍如何用开发基于angular的应用,结合rosbridge和turtlebot3进行交互。

本文所用的环境是ubuntu20.04 (ros-noetic),angular12

 

一. turtlebot3的仿真

在ROS上安装turtlebot3仿真环境:

首先安装基本pkg:

$ cd ~/catkin_ws/src/
$ git clone https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git -b noetic-devel
$ git clone  https://github.com/ROBOTIS-GIT/turtlebot3.git -b noetic-devel
$ cd ~/catkin_ws && catkin_make

之后安装sim:

$ cd ~/catkin_ws/src/
$ git clone https://github.com/ROBOTIS-GIT/turtlebot3_simulations.git
$ cd ~/catkin_ws && catkin_make

需要以下步骤来配置下系统环境:

source /opt/ros/noetic/setup.bash
source /home/akoubaa/catkin_ws/devel/setup.bash
export TURTLEBOT3_MODEL=waffle
export SVGA_VGPU10=0

其中SVGA_VGPU10是针对Nvidia显卡的选项,如果仍然有显示错误,更新显卡驱动或尝试将值改成1。

开启turtlebot3的gazebo仿真:

 

$ roslaunch turtlebot3_gazebo turtlebot3_house.launch

 

 

 

二. Ros消息的发布 

本节介绍用angular遥控器组件开发一个turtlebot3遥控器

首先安装遥控器的package:

npm install --save-dev ngx-joystick

所安装的angular控件是一个摇杆,支持三种遥感模式,这里示例static模式遥感的使用和ros消息的发布:

html template开发如下:

 1 <div>
 2   <div class="container noselect">
 3     <div class="zone">
 4       <ngx-joystick #staticJoystic
 5                     [options]="staticOptions"
 6                     (start)="onStartStatic($event)"
 7                     (end)="onEndStatic($event)"
 8                     (move)="onMoveStatic($event)"
 9                     (plainUp)="onPlainUpStatic($event)"
10                     (plainDown)="onPlainDownStatic($event)"
11                     (plainLeft)="onPlainLeftStatic($event)"
12                     (plainRight)="onPlainRightStatic($event)"></ngx-joystick>
13     </div>
14     <div *ngIf="staticOutputData && showInfo" class="zone-absolute">
15       <div style="float: right;">
16         <div>xPos: {{ staticOutputData.position.x | number:'3.2-2'}}</div>
17         <div>yPos: {{ staticOutputData.position.y | number:'3.2-2'}}</div>
18         <div>dirEvent: {{ directionStatic }}</div>
19         <div>interact: {{ interactingStatic }}</div>
20       </div>ng
21       <div>frontXPos: {{ staticOutputData.instance.frontPosition.x | number:'2.5-5'}}</div>
22       <div>frontYPos: {{ staticOutputData.instance.frontPosition.y | number:'2.5-5'}}</div>
23       <div>force: {{ staticOutputData.force | number:'1.5-5'}}</div>
24       <div>angle-deg: {{ staticOutputData.angle.degree | number:'3.5-5'}}</div>
25       <div>angle-rad: {{ staticOutputData.angle.radian }}</div>
26       <div>direction: {{ staticOutputData.direction ? staticOutputData.direction.angle : ''}}</div>
27       <div>distance: {{ staticOutputData.distance }}</div>
28     </div>
29   </div>
30 </div>

遥控器逻辑:

 1 import {Component, OnInit, ViewChild, Output, EventEmitter} from '@angular/core';
 2 import { JoystickEvent, NgxJoystickComponent } from 'ngx-joystick';
 3 import {Joystick, JoystickManagerOptions, JoystickOutputData, Position} from 'nipplejs';
 4 
 5 @Component({
 6   selector: 'app-teleop',
 7   templateUrl: './teleop.component.html',
 8   styleUrls: ['./teleop.component.css']
 9 })
10 export class TeleopComponent implements OnInit {
11 
12   @ViewChild('staticJoystic') staticJoystick: NgxJoystickComponent | undefined;
13 
14   @Output() moveEvent = new EventEmitter<JoystickOutputData>();
15 
16   staticOptions: JoystickManagerOptions = {
17     mode: 'static',
18     position: { left: '50%', top: '50%' },
19     color: '#222222',
20   };
21 
22   staticOutputData: JoystickOutputData | undefined;
23 
24   directionStatic: string;
25   interactingStatic: boolean;
26   showInfo: boolean = false;
27 
28   constructor() {
29     this.directionStatic = '';
30     this.interactingStatic = false;
31   }
32 
33   ngOnInit() {
34   }
35 
36   onStartStatic(event: JoystickEvent) {
37     this.interactingStatic = true;
38   }
39 
40   onEndStatic(event: JoystickEvent) {
41     if (this.staticOutputData) {
42       let data = this.staticOutputData;
43       data.vector.x = 0;
44       data.vector.y = 0;
45       this.moveEvent.emit(data);
46     }
47     this.interactingStatic = false;
48   }
49 
50   onMoveStatic(event: JoystickEvent) {
51     this.staticOutputData = event.data;
52     this.moveEvent.emit(this.staticOutputData);
53   }
54 
55   onPlainUpStatic(event: JoystickEvent) {
56     this.directionStatic = 'UP';
57   }
58 
59   onPlainDownStatic(event: JoystickEvent) {
60     this.directionStatic = 'DOWN';
61   }
62 
63   onPlainLeftStatic(event: JoystickEvent) {
64     this.directionStatic = 'LEFT';
65   }
66 
67   onPlainRightStatic(event: JoystickEvent) {
68     this.directionStatic = 'RIGHT';
69   }
70 
71 }

 

三. Ros消息的监听

这里监视turtlebot3 gazebo中的速度和位置:

为了正确显示角度,首先需要安装THREE这个库来将四元数转化成欧拉角:

npm install --save-dev three

html template:

 1 <h3 class="text-center">Robot Position</h3>
 2 <div class="row">
 3   <div class="col">
 4     <h4 class="mt-4">Position</h4>
 5     <p class="mt-0">x:{{this.state.x}}</p>
 6     <p class="mt-0">y:{{this.state.y}}</p>
 7     <p class="mt-0">orientation:{{this.state.orientation}}</p>
 8   </div>
 9   <div class="col">
10     <h4 class="mt-4">Velocities</h4>
11     <p class="mt-0">Linear Velocity:{{this.state.linear_velocity}}</p>
12     <p class="mt-0">Angular Velocity:{{this.state.angular_velocity}}</p>
13   </div>
14 </div>

逻辑:

 1 import {Component, EventEmitter, Input, OnDestroy, OnInit, Output} from '@angular/core';
 2 import {JoystickOutputData} from "nipplejs";
 3 import * as THREE from "three";
 4 
 5 declare var ROSLIB: any;
 6 declare var ROS2D: any;
 7 
 8 @Component({
 9   selector: 'app-robot-state',
10   templateUrl: './robot-state.component.html',
11   styleUrls: ['./robot-state.component.css']
12 })
13 export class RobotStateComponent implements OnInit, OnDestroy {
14 
15   @Input() ros: any = null;
16 
17   intervalId: number = 0;
18 
19   pose_subscriber: any = null;
20 
21   odom_subscriber: any = null;
22 
23   position: any = null;
24 
25   odom: any = null;
26 
27   state = {
28     x: 0,
29     y: 0,
30     orientation: '0',
31     linear_velocity: 0,
32     angular_velocity: 0,
33   }
34 
35   constructor() {
36 
37   }
38 
39   ngOnInit(): void {
40     this.intervalId = setInterval(()=> {
41       if (this.position){
42         this.state.x = this.position.pose.pose.position.x.toFixed(2);
43         this.state.y = this.position.pose.pose.position.y.toFixed(2);
44         this.state.orientation = (RobotStateComponent.getOrientationFromQuaternion(this.position.pose.pose.orientation)).toFixed(2);
45       }
46       if (this.odom) {
47         this.state.linear_velocity = this.odom.twist.twist.linear.x.toFixed(2);
48         this.state.angular_velocity = this.odom.twist.twist.angular.z.toFixed(2);
49       }
50     }, 100);
51 
52     if (this.ros) {
53       this.pose_subscriber = new ROSLIB.Topic({
54         ros: this.ros,
55         name: "/amcl_pose",
56         messageType: '/geometry_msgs/PoseWithCovarianceStamped',
57       });
58 
59       this.pose_subscriber.subscribe((message: any) => {
60         this.position = message;
61       });
62 
63       this.odom_subscriber = new ROSLIB.Topic({
64         ros: this.ros,
65         name: "/odom",
66         messageType: 'nav_msgs/Odometry',
67       });
68 
69       this.odom_subscriber.subscribe((message: any) => {
70         this.odom = message;
71       });
72     }
73   }
74 
75   ngOnDestroy() {
76     clearInterval(this.intervalId);
77   }
78 
79   private static getOrientationFromQuaternion(ros_orientation_quaternion: any): number {
80     const q = new THREE.Quaternion(ros_orientation_quaternion.x, ros_orientation_quaternion.y,
81       ros_orientation_quaternion.z, ros_orientation_quaternion.w);
82     const RPY = new THREE.Euler().setFromQuaternion(q);
83     return RPY.z * (180 / Math.PI);
84   }
85 
86 }

遥控器和监控panel效果如下:

 

 

 

 

四. 基于ROS2D的地图显示和导航

启动Rviz和导航功能:

$ roslaunch turtlebot3_navigation turtlebot3_navigation.launch map_file:=/your/path/to/map/tb3_house_map.yaml

安装ros2d:

npm install --save-dev ros2d

这里要注意ros2d的两个依赖并不会自动安装:easeljs 和 eventemitter2,分别到相应的git地址中找到相应的代码并放入当前项目中。要实现地图显示和导航还需要nav2d,也从git中找到代码:

 

 在angular.json中添加js代码:

 

 开发地图component,代码如下:

 html 模板:

<div id="nav_div"></div>

逻辑代码:

 1 import {Component, Input, OnInit} from '@angular/core';
 2 //import {ROS2D} from "../connection.component";
 3 
 4 declare var ROS2D: any;
 5 declare var NAV2D: any;
 6 
 7 @Component({
 8   selector: 'app-map',
 9   templateUrl: './map.component.html',
10   styleUrls: ['./map.component.css']
11 })
12 export class MapComponent implements OnInit {
13 
14   @Input() ros: any = null;
15 
16   constructor() { }
17 
18   ngOnInit(): void {
19     this.viewMap();
20   }
21 
22   viewMap() {
23     const viewer = new ROS2D.Viewer({
24       divID: "nav_div",
25       width: 640,
26       height: 480,
27     });
28     console.log(viewer);
29     const navCli = new NAV2D.OccupancyGridClientNav({
30       ros: this.ros,
31       rootObject: viewer.scene,
32       viewer: viewer,
33       serverName: "/move_base",
34       widthOrientation: true,
35     });
36   }
37 
38 }

效果:

 

 

注:本文中所有component的ROS object都是从主页面中注入的,可以确保所有模块所用ROS对象的一致性

posted @ 2021-12-26 16:08  Asp1rant  阅读(680)  评论(0编辑  收藏  举报