平台
简介
基于Blockly
与ROSWeb
实现可视化turtlesim
的Web
界面基础开发。
环境搭建
配置Vue环境
安装以下依赖:1
2
3npm install blockly
npm install --save eval5
npm i element-ui -S
修改src->main.js
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import router from './router'
Vue.config.productionTip = false
Vue.use(ElementUI)
Vue.config.ignoredElements.push('xml')
Vue.config.ignoredElements.push('block')
Vue.config.ignoredElements.push('field')
Vue.config.ignoredElements.push('category')
Vue.config.ignoredElements.push('sep')
Vue.config.ignoredElements.push('value')
Vue.config.ignoredElements.push('statement')
Vue.config.ignoredElements.push('mutation')
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
这里主要是导入ElementUI
和注册Blockly
相关组件。
配置Blockly环境
将node_modules->blockly->media
所有的文件复制到static
文件夹下,这里主要是blockly
所需的媒体资源文件。
配置ROSWeb环境
在static
中新建文件夹rosweb
,用于存放所需要的第三方JavaScript
文件。
其中roslib.js
和ros3d.js
为ros-web
提供的库文件,剩余JavaScript
文件为用于绘制3D
可视化模型的库文件(主要为three.js
框架及其依赖库)。
然后在index.html
入口文件中,将这些文件添加<head>
标签中。1
2
3
4
5
6<script src="./static/rosweb/three.js"></script>
<script src="./static/rosweb/ColladaLoader.js"></script>
<script src="./static/rosweb/STLLoader.js"></script>
<script src="./static/rosweb/eventemitter2.min.js"></script>
<script src="./static/rosweb/roslib.js"></script>
<script src="./static/rosweb/ros3d.js"></script>
ESlint代码规范
这里针对ESlint
做一些代码规范处理,并添加ROSWeb
全局变量。修改.eslintrc.js
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint',
sourceType: 'module'
},
env: {
browser: true,
node: true,
es6: true,
},
extends: ['plugin:vue/recommended', 'eslint:recommended'],
// add your custom rules here
//it is base on https://github.com/vuejs/eslint-config-vue
rules: {
"vue/max-attributes-per-line": [2, {
"singleline": 10,
"multiline": {
"max": 1,
"allowFirstLine": false
}
}],
"vue/singleline-html-element-content-newline": "off",
"vue/multiline-html-element-content-newline":"off",
"vue/name-property-casing": ["error", "PascalCase"],
"vue/no-v-html": "off",
'accessor-pairs': 2,
'arrow-spacing': [2, {
'before': true,
'after': true
}],
'block-spacing': [2, 'always'],
'brace-style': [2, '1tbs', {
'allowSingleLine': true
}],
'camelcase': [0, {
'properties': 'always'
}],
'comma-dangle': [2, 'never'],
'comma-spacing': [2, {
'before': false,
'after': true
}],
'comma-style': [2, 'last'],
'constructor-super': 2,
'curly': [2, 'multi-line'],
'dot-location': [2, 'property'],
'eol-last': 2,
'eqeqeq': ["error", "always", {"null": "ignore"}],
'generator-star-spacing': [2, {
'before': true,
'after': true
}],
'handle-callback-err': [2, '^(err|error)$'],
'indent': [2, 2, {
'SwitchCase': 1
}],
'jsx-quotes': [2, 'prefer-single'],
'key-spacing': [2, {
'beforeColon': false,
'afterColon': true
}],
'keyword-spacing': [2, {
'before': true,
'after': true
}],
'new-cap': [2, {
'newIsCap': true,
'capIsNew': false
}],
'new-parens': 2,
'no-array-constructor': 2,
'no-caller': 2,
'no-console': 'off',
'no-class-assign': 2,
'no-cond-assign': 2,
'no-const-assign': 2,
'no-control-regex': 0,
'no-delete-var': 2,
'no-dupe-args': 2,
'no-dupe-class-members': 2,
'no-dupe-keys': 2,
'no-duplicate-case': 2,
'no-empty-character-class': 2,
'no-empty-pattern': 2,
'no-eval': 2,
'no-ex-assign': 2,
'no-extend-native': 2,
'no-extra-bind': 2,
'no-extra-boolean-cast': 2,
'no-extra-parens': [2, 'functions'],
'no-fallthrough': 2,
'no-floating-decimal': 2,
'no-func-assign': 2,
'no-implied-eval': 2,
'no-inner-declarations': [2, 'functions'],
'no-invalid-regexp': 2,
'no-irregular-whitespace': 2,
'no-iterator': 2,
'no-label-var': 2,
'no-labels': [2, {
'allowLoop': false,
'allowSwitch': false
}],
'no-lone-blocks': 2,
'no-mixed-spaces-and-tabs': 2,
'no-multi-spaces': 2,
'no-multi-str': 2,
'no-multiple-empty-lines': [2, {
'max': 1
}],
'no-native-reassign': 2,
'no-negated-in-lhs': 2,
'no-new-object': 2,
'no-new-require': 2,
'no-new-symbol': 2,
'no-new-wrappers': 2,
'no-obj-calls': 2,
'no-octal': 2,
'no-octal-escape': 2,
'no-path-concat': 2,
'no-proto': 2,
'no-redeclare': 2,
'no-regex-spaces': 2,
'no-return-assign': [2, 'except-parens'],
'no-self-assign': 2,
'no-self-compare': 2,
'no-sequences': 2,
'no-shadow-restricted-names': 2,
'no-spaced-func': 2,
'no-sparse-arrays': 2,
'no-this-before-super': 2,
'no-throw-literal': 2,
'no-trailing-spaces': 2,
'no-undef': 2,
'no-undef-init': 2,
'no-unexpected-multiline': 2,
'no-unmodified-loop-condition': 2,
'no-unneeded-ternary': [2, {
'defaultAssignment': false
}],
'no-unreachable': 2,
'no-unsafe-finally': 2,
'no-unused-vars': 'off',
// 'no-unused-vars': [2, {
// 'vars': 'all',
// 'args': 'none'
// }],
'no-useless-call': 2,
'no-useless-computed-key': 2,
'no-useless-constructor': 2,
'no-useless-escape': 0,
'no-whitespace-before-property': 2,
'no-with': 2,
'one-var': [2, {
'initialized': 'never'
}],
'operator-linebreak': [2, 'after', {
'overrides': {
'?': 'before',
':': 'before'
}
}],
'padded-blocks': [2, 'never'],
'quotes': [2, 'single', {
'avoidEscape': true,
'allowTemplateLiterals': true
}],
'semi': [2, 'never'],
'semi-spacing': [2, {
'before': false,
'after': true
}],
'space-before-blocks': [2, 'always'],
'space-before-function-paren': [2, 'never'],
'space-in-parens': [2, 'never'],
'space-infix-ops': 2,
'space-unary-ops': [2, {
'words': true,
'nonwords': false
}],
'spaced-comment': [2, 'always', {
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
}],
'template-curly-spacing': [2, 'never'],
'use-isnan': 2,
'valid-typeof': 2,
'wrap-iife': [2, 'any'],
'yield-star-spacing': [2, 'both'],
'yoda': [2, 'never'],
'prefer-const': 2,
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'object-curly-spacing': [2, 'always', {
objectsInObjects: false
}],
'array-bracket-spacing': [2, 'never'],
},
"globals":{
"ROSLIB": true,
"ROS3D": true
}
}
工具类
blocklytools
新建src->utils->blocklytools->myBlockly.js
和src->utils->blocklytools->toolboxStyle.css.js
:
myBlockly.js
1 | // 引入Blockly |
这里主要重新设计了Blockly
的样式,并自定义了一个turtlesim_move
的代码块。
toolboxStyle.css
1 | /* 侧边栏样式 */ |
这里主要是Blockly
侧边栏的样式修改。
roswebtools
turtlesim.js
1 | export const callTurtlesimMoveService = (ros, x, y, z) => { |
由于ROS
话题(topic
)和服务(service
)的设计机制,当运行多个话题/服务时,只会运行最后一个,因此需要在应用层做一些逻辑处理。
由于服务具有返回值的特性,可以根据返回值信息来判断是否进行下一个服务的调用,因此本项目ROS
端的接口采用服务(service
)的机制。
本项目采用ES7
提出的async
和await
异步特性,并结合Promise
处理正确/异常信息,从而让其每次调用服务(service
)时,都会等待其返回状态,并根据返回状态做下一步的处理。
首先需要返回一个Promise
对象,其包含2个参数,即resolve
(解析)和reject
(拒绝)。然后通过ROSWeb
提供的callService(request, callback, failedCallback)
判断服务(service
)的完成状态。如果返回值为true
,则解析并返回结果,如果返回值为false
,则抛出异常,并结束该回调函数。
最后在Web
端通过async
和await
接收并处理该Promise
(见下文分析)。
Web主界面
HelloWorld.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407<template>
<div id="visualProgram" class="visualProgram">
<el-row
:gutter="10"
type="flex">
<el-col :span="12">
<!-- blockly工作区 -->
<div id="blocklyDiv" class="div-blocklyDiv">
<!-- blockly工具栏 -->
<!-- xml不能在浏览器中正常渲染,因此需要设置为不可见 -->
<xml id="toolbox" style="display: none">
<category name="逻辑控制" colour="%{BKY_LOGIC_HUE}">
<category name="If" colour="#008B00">
<block type="controls_if" />
<block type="controls_if">
<mutation else="1" />
</block>
<block type="controls_if">
<mutation elseif="1" else="1" />
</block>
</category>
<category name="Boolean" colour="%{BKY_LOGIC_HUE}">
<block type="logic_compare" />
<block type="logic_operation" />
<block type="logic_negate" />
<block type="logic_ternary" />
</category>
<category name="Loop" colour="%{BKY_LOOPS_HUE}">
<block type="controls_repeat_ext">
<value name="TIMES">
<block type="math_number">
<field name="NUM">10</field>
</block>
</value>
</block>
<block type="controls_whileUntil" />
<block type="controls_for">
<field name="VAR">i</field>
<value name="FROM">
<block type="math_number">
<field name="NUM">1</field>
</block>
</value>
<value name="TO">
<block type="math_number">
<field name="NUM">10</field>
</block>
</value>
<value name="BY">
<block type="math_number">
<field name="NUM">1</field>
</block>
</value>
</block>
<block type="controls_forEach" />
<block type="controls_flow_statements" />
</category>
</category>
<category name="数学运算" colour="%{BKY_MATH_HUE}">
<block type="math_arithmetic" />
<block type="math_single" />
<block type="math_trig" />
<block type="math_number_property" />
<block type="math_round" />
<block type="math_on_list" />
<block type="math_modulo" />
<block type="math_constrain">
<value name="LOW">
<block type="math_number">
<field name="NUM">1</field>
</block>
</value>
<value name="HIGH">
<block type="math_number">
<field name="NUM">100</field>
</block>
</value>
</block>
<block type="math_random_int">
<value name="FROM">
<block type="math_number">
<field name="NUM">1</field>
</block>
</value>
<value name="TO">
<block type="math_number">
<field name="NUM">100</field>
</block>
</value>
</block>
<block type="math_random_float" />
<block type="math_atan2" />
</category>
<category name="列表运算" colour="%{BKY_LISTS_HUE}">
<block type="lists_create_empty" />
<block type="lists_create_with" />
<block type="lists_repeat">
<value name="NUM">
<block type="math_number">
<field name="NUM">5</field>
</block>
</value>
</block>
<block type="lists_length" />
<block type="lists_isEmpty" />
<block type="lists_indexOf" />
<block type="lists_getIndex" />
<block type="lists_setIndex" />
</category>
<category name="文本控制" colour="%{BKY_TEXTS_HUE}">
<block type="text_length" />
<block type="text_print" />
</category>
<category name="常用变量" colour="#556B2F">
<block type="math_number">
<field name="NUM">1</field>
</block>
<block type="math_number">
<field name="NUM">0</field>
</block>
<block type="math_number">
<field name="NUM">-1</field>
</block>
<block type="logic_boolean" />
<block type="logic_null" />
<block type="math_constant" />
<block type="text" />
</category>
<category name="ROS控制" colour="#FF7F00">
<block type="turtlesim_move" />
</category>
</xml>
<div class="div-run-code">
<el-button class="el-button-run-code" icon="el-icon-video-play" @click="runJavascriptCode" />
<el-button class="el-button-run-code" icon="el-icon-refresh" @click="refreshJavascriptCode" />
<el-button class="el-button-run-code" icon="el-icon-video-pause" @click="stopJavascriptCode" />
<el-button class="el-button-run-code" icon="el-icon-circle-close" @click="clearWorkspace" />
<!-- <el-button @click="test">测试</el-button> -->
</div>
</div>
</el-col>
<el-col :span="6">
<div class="div-blockly-code">
<!-- blockly代码区 -->
<el-input
v-model="blocklyCodeMessage"
:disabled="true"
:rows="39"
type="textarea"
class="el-input-blockly-code"
/>
</div>
</el-col>
<el-col :span="6">
<div class="div-blockly-console">
<!-- blockly代码控制台 -->
<el-input
v-model="blocklyConsoleMessage"
:disabled="true"
:rows="39"
type="textarea"
class="el-input-blockly-console"
/>
</div>
<div class="div-clear-console">
<el-button style="width:28px;height:28px;padding:0px;font-size:25px" icon="el-icon-circle-close" @click="clearConsoleMessage" />
</div>
</el-col>
</el-row>
</div>
</template>
<script>
// https://github.com/bplok20010/eval5
import { Interpreter } from 'eval5'
import * as myblock from '@/utils/blocklytools/myBlockly'
// 引入Blockly
import Blockly from 'blockly'
// 引入想要转换的语言,语言有php python dart lua javascript
import 'blockly/javascript'
import 'blockly/python'
// 引入语言包并使用
import * as hans from 'blockly/msg/zh-hans'
Blockly.setLocale(hans)
import * as turtlesim from '@/utils/roswebtools/turtlesim'
export default {
name: 'HelloWorld',
data() {
return {
workspace: null,
blocklyCodeMessage: '',
blocklyConsoleMessage: '',
jsCode: null,
ros: null,
isConnected: false,
isRunWamNode: false,
wamServerIp: '192.168.31.99',
isDone: true
}
},
mounted() {
this.initBlockly()
myblock.initMyBlockly()
// 将自定义函数添加至window中,否则解析时,无法识别函数
window.turtlesimMove = this.turtlesimMove
},
created() {
this.ros = new ROSLIB.Ros({
url: 'ws://' + this.wamServerIp + ':9090'
})
this.ros.on('connection', () => {
this.isConnected = true
this.$message.success('连接ROS成功!')
this.blocklyConsoleMessage += '连接ROS成功!' + '\n'
})
this.ros.on('error', (e) => {
this.isConnected = false
this.$message.error('连接ROS失败!')
this.blocklyConsoleMessage += '连接ROS失败!' + '\n'
})
this.ros.on('close', () => {
this.isConnected = false
this.$message.error('关闭ROS连接!')
this.blocklyConsoleMessage += '关闭ROS连接!' + '\n'
})
},
beforeDestroy() {
// 关闭ros连接
if (this.isConnected) {
this.ros.close()
}
},
methods: {
initBlockly() {
this.workspace = myblock.initBlockly('blocklyDiv', 'toolbox')
// 工作区监听代码生成器
this.workspace.addChangeListener(this.myUpdateFunction)
var toolbox = Blockly.getMainWorkspace().getToolbox()
},
// 代码生成器
myUpdateFunction(event) {
var codeJs = Blockly.JavaScript.workspaceToCode(this.workspace)
this.blocklyCodeMessage = codeJs
},
async runJavascriptCode() {
// const interpreter = new Interpreter(window, {
// timeout: 1000
// })
// 实例化JavaScript解释器eval5
const interpreter = new Interpreter(window)
// Blokly获取JavaScript代码
this.jsCode = Blockly.JavaScript.workspaceToCode(this.workspace)
try {
// 代码预检查
var isOk = this.checkCode()
if (isOk) {
// 按分号切分指令
var stringList = this.jsCode.split(';')
for (var i = 0; i < stringList.length - 1; i++) {
// await 执行evaluate()
var result = await interpreter.evaluate(stringList[i])
this.blocklyConsoleMessage += result + '\n'
}
// 异常处理
if (this.isDone === false) {
this.$message.error('运行错误!')
}
} else {
// 预检查错误
this.blocklyConsoleMessage += '运行错误' + '\n'
}
} catch (e) {
// 运行错误
this.blocklyConsoleMessage += e + '\n'
}
},
refreshJavascriptCode() {
this.isDone = true
this.$message.success('重置程序')
},
stopJavascriptCode() {
this.$message.error('停止运行')
},
clearWorkspace() {
this.workspace.clear()
this.$message.success('清除工作空间')
},
clearConsoleMessage() {
this.blocklyConsoleMessage = ''
},
async turtlesimMove(x, y) {
if (this.isDone) {
try {
this.isDone = false
await turtlesim.callTurtlesimMoveService(this.ros, x, y, 0)
this.isDone = true
return 'turtlesim' + '向右移动了' + x + ',向上移动了' + y + '.'
} catch (error) {
this.isDone = false
return '运行错误'
}
}
},
checkCode() {
// 没有错误
if (this.jsCode.length === 0) {
this.$message.error('请输入指令')
return false
} else if (this.jsCode.indexOf('WAMERROR') === -1) {
return true
} else {
return false
}
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#visualProgram .el-input-blockly-code /deep/ .el-textarea__inner{
color: black;
background-color: white
}
#visualProgram .el-input-blockly-console /deep/ .el-textarea__inner{
color: black;
background-color: white
}
</style>
<!-- 这里需要是全局样式,即去掉scoped -->
<!-- 这里加上页面的id https://www.jianshu.com/p/4ff9a5397427 -->
<style>
@import "../utils/blocklytools/toolboxStyle.css";
.div-blocklyDiv{
width:800px;
height:850px;
margin-top: 20px;
margin-left: 20px;
position: relative;
border: 2px solid #008B93;
}
.div-run-code{
position: absolute;
right: 300px;
/* bottom: 800px; */
bottom: 30px;
z-index: 2;
}
.el-button-run-code{
width:40px;
height:40px;
padding:0px;
font-size:30px
}
.div-blockly-code{
margin-top: 20px;
height:850px;
color: #008B93;
border: 2px solid #008B93;
}
.div-blockly-console{
margin-top: 20px;
margin-right: 10px;
height:850px;
border: 2px solid #008B93;
}
.el-input-blockly-code{
width: 99%;
margin: 2px;
border: 1px solid #008B93;
}
.el-input-blockly-console{
width: 99%;
margin: 2px;
border: 1px solid #008B93;
}
.div-clear-console{
position: absolute;
right: 25px;
top: 30px;
z-index: 2;
}
</style>
这里比较重要的代码部分为runJavascriptCode()
函数和blockly
生成的自定义函数,这里为turtlesimMove()
。
首先需要将turtlesimMove()
声明为async
的异步函数,并通过await
调用ROSWeb
的接口,保证每次都会等待服务(service
)的返回状态。
为了统一管理服务(service
)返回的状态,这里通过全局变量isDone
去处理,即如果正常执行完服务(service
),则将该变量置为true
,否则一旦捕获到异常信息,则置为false
。
由于eval5
并不支持在内部解析await
,因此需要将blockly
生成的代码字符串jsCode
按行解析成单句,通过将runJavascriptCode()
声明为async
的异步函数,并使用await
等待evaluate()
的完成状态。
这里会根据全局变量isDone
来处理异常情况,一旦ROSWeb
接收到异常的状态,会给出用户的错误信息提示。
Ros Service
最后需要在ROS
中重写服务(service
)的接口。
Service类型
在ROS
工程文件夹下,新建srv->turtlesim_move_srv.srv
:1
2
3
4
5float32 x
float32 y
float32 z
---
bool result
Service接口
在ROS
工程文件夹下,新建src->turtlesim_move.cpp
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
ros::Publisher turtle_vel_pub;
// service回调函数,输入参数req,输出参数res
bool moveCallback(turtlesim_move::turtlesim_move_srv::Request &req,
turtlesim_move::turtlesim_move_srv::Response &res)
{
geometry_msgs::Twist vel_msg;
vel_msg.linear.x = req.x;
vel_msg.linear.y = req.y;
vel_msg.linear.z = req.z;
turtle_vel_pub.publish(vel_msg);
// 设置反馈数据
sleep(2);
res.result = true;
return res.result;
}
int main(int argc, char **argv)
{
// ROS节点初始化
ros::init(argc, argv, "turtlesim_move_server");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个名为/turtlesim_move的server,注册回调函数moveCallback
ros::ServiceServer move_service = n.advertiseService("/turtlesim_move", moveCallback);
// 创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10
turtle_vel_pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel", 10);
ros::spin();
return 0;
}
这里主要调用/turtle1/cmd_vel
话题实现turtlesim
的移动。
这里的sleep(2);
只是为了测试功能,实际的工程项目中,需要根据实际情况返回true/false
状态。
编译运行
CMakeLists.txt1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61cmake_minimum_required(VERSION 3.0.2)
project(turtlesim_move)
## Compile as C++11, supported in ROS Kinetic and newer
# add_compile_options(-std=c++11)
## Find catkin macros and libraries
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
## is used, also find other catkin packages
find_package(catkin REQUIRED COMPONENTS
geometry_msgs
roscpp
rospy
std_msgs
turtlesim
message_generation
)
## Generate services in the 'srv' folder
add_service_files(FILES turtlesim_move_srv.srv)
## Generate added messages and services with any dependencies listed here
generate_messages(
DEPENDENCIES
geometry_msgs
std_msgs
)
###################################
## catkin specific configuration ##
###################################
## The catkin_package macro generates cmake config files for your package
## Declare things to be passed to dependent projects
## INCLUDE_DIRS: uncomment this if your package contains header files
## LIBRARIES: libraries you create in this project that dependent projects also need
## CATKIN_DEPENDS: catkin_packages dependent projects also need
## DEPENDS: system dependencies of this project that dependent projects also need
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES turtlesim_move
# CATKIN_DEPENDS geometry_msgs roscpp rospy std_msgs turtlesim
# DEPENDS system_lib
)
###########
## Build ##
###########
## Specify additional locations of header files
## Your package locations should be listed before other locations
include_directories(
# include
${catkin_INCLUDE_DIRS}
)
## Specify libraries to link a library or executable target against
# target_link_libraries(${PROJECT_NAME}_node
# ${catkin_LIBRARIES}
# )
target_link_libraries(turtlesim_move ${catkin_LIBRARIES})
功能测试
依次启动ROS
接口:1
2
3roslaunch rosbridge_server rosbridge_websocket.launch
rosrun turtlesim turtlesim_node
rosrun turtlesim_move turtlesim_move
拖拽blockly
控件,输入正确的参数,运行: