赞
踩
当我们探索未来的交通系统和智能交通解决方案时,车辆到一切(Vehicle-to-Everything, V2X)通信技术显得尤为重要。V2X是指在车辆与车辆(V2V)、车辆与基础设施(V2I)、车辆与行人(V2P)以及车辆与网络(V2N)之间进行的通信。这种技术能够提高道路安全,优化交通流量,减少拥堵,提升驾驶体验,并为自动驾驶汽车的实现打下基础。
为了准确模拟和分析V2X通信中的复杂交互,需要使用一些专用的仿真工具:
OMNeT++ 是一个公开源码的网络仿真框架,提供了广泛的工具集和功能,用于构建复杂的网络和其他分布式系统。OMNeT++的灵活性和模块化使其成为研究和模拟通信网络,特别是V2X通信网络的理想选择。
INET 是OMNeT++的一个扩展模型库,专注于互联网协议和网络技术的仿真。INET提供了大量的网络协议模型,如TCP/IP、路由协议等,允许研究者构建和测试各种网络架构和服务。
SUMO (Simulation of Urban MObility) 是一个开源的交通仿真软件,用于模拟城市的车辆流动。通过SUMO,研究人员可以创建详细的城市交通场景,包括道路网络、交通信号灯、车辆行为等,来分析不同交通策略和管理措施的效果。
VEINS 是一个允许OMNeT++和SUMO之间进行联合仿真的框架,专门用于车辆通信系统的研究。它使得OMNeT++模拟的通信网络和SUMO模拟的移动车辆能够实时交互,从而实现对V2X通信场景的全面仿真。
使用OMNeT++结合VEINS、INET和SUMO进行联合仿真,能够在复杂的城市交通环境中准确模拟V2X通信。这种仿真可以帮助研究者评估V2X技术在实际应用中的表现,如通信延迟、系统可靠性和安全性等。此外,仿真结果还可以指导政策制定者和工程师设计更有效的交通管理策略和智能交通系统,推动智能交通技术的发展,为实现无缝、安全和高效的未来交通网络提供支持。
在这篇博文中,我将尝试:
关于这四个软件的下载安装,以下两篇博文是很好的参考:
https://www.cnblogs.com/Xylona/p/17779621.html
(veins5.0+sumo1.2.0+OMNeT++5.5.1)车载自组织网络仿真环境安装配置教程(一步一步)_sumo1.3.0+veins5.2+omnet++5.5.1-CSDN博客
我最终使用的版本:
在分别下载好这四个版本的软件后,进入OMNet++文件夹,打开configure.user,修改PREFER_CLANG的值为no
共配置四条:
打开OMNet++中的“mingwenv.cmd”,并按下任意键开始解压;
解压完成后分别输入“./configure”和“make”进行编译,make的时间大概要一个小时
在“mingwenv.cmd”中输入“omnetpp”打开IDE界面
点击左上角File -> Import:
导入inet和veins:
注意!对于veins要勾选search for nested projects!!
点击inet -> examples -> aodv -> omnetpp.ini,然后右键Run as Omnet ++ simulation就可以看到示例仿真:
在“mingwenv.cmd”中输入以下指令使veins连接上sumo:
C:/Users/Majiaming/Desktop/WESTERN/9038_wireless_comm/project/project_new/veins-veins-5.2/bin/veins_launchd -vv -c C:/Users/Majiaming/Desktop/WESTERN/9038_wireless_comm/project/project_new/sumo-1.10.0/bin/sumo.exe
点击左侧veins -> examples -> veins -> omnetpp.ini,然后右键Run as Omnet ++ simulation就可以看到示例仿真:
点击Run后:
现在,成功完成了环境搭建,接下来就要开始创建一个属于我的项目。
关于接下来的大体步骤,我参考了以下油管的视频,但是如果仿真软件的版本和我相差不大,建议参考我的版本,因为我按照我下载的这一套VEINS,SUMO,INET,OMNET++版本完全按照视频操作会出现一些问题,所以建议有什么不会再去参考油管的视频:
How to Create a New OMNET++ Project That Works with INET and Veins (youtube.com)
How to Simulate a V2V Network using OMNET++, INET, and Veins - YouTube
How to Add A Custom Sumo Simulation to OMNET++ (youtube.com)
现在,这个新的project已经成功的refer到了veins 和 inet,但是,问题来了,如何才能将二者结合起来?答案是:veins中提供了一个sub-project,名字是veins_inet,其中提供了大量现成的cc代码和一个简单的示例,这个sub-project提供了一个将二者结合的很好例子,这也是为什么刚刚import veins的时候要同时Import veins_inet这个子项目!
① 找到刚刚import的veins_inet子项目, 找到 examples -> veins_inet 拷贝到新项目的simulation下:
出现了大量的红色叉叉,这是因为.ned文件中的package的名字不正确
② 修改.ned文件中的package name:
- //在所有.ned文件中:(其实就两个)
- //将开头的
- package XXXXXXX.veins_inet;
- //修改为
- package _9038_project.simulations.veins_inet;
③ 可以通过运行当前路径下的omnetpp.ini来测试是否成功:
由于此处包含veins,别忘了在OMNETT++根目录下的mingwenv.cmd中输入以下代码连接SUMO:
C:/Users/Majiaming/Desktop/WESTERN/9038_wireless_comm/project/project_new/veins-veins-5.2/bin/veins_launchd -vv -c C:/Users/Majiaming/Desktop/WESTERN/9038_wireless_comm/project/project_new/sumo-1.10.0/bin/sumo.exe
可见,成功在自己创建的项目中运行了veins_inet的例程仿真!
现在,已经可以成功的在自己创建的project里运行结合veins_inet例子下的仿真,但是例子中的sumo地图并不是我想要的,下一步是根据自己的地图,实现veins,inet,sumo的联合仿真
在新建项目的simulations下再创建一个“my_veins_inet”的文件夹,把刚刚“veins_inet”的内容全部复制进来:
(同样,记得修改两个.ned文件中的package名,此处不再展开!)
然后,将square.net.xml; square.poly.xml; square.rou.xml; obstacles.xml删除,剩下的留着待会修改
此处我选择了留学所在城市的一家costco附近的地图
网站:https://www.openstreetmap.org/export#map=16/42.9852/-81.2900
① 选定区域后点击左侧的“Export”导出.osm文件
② 通过python脚本分别生成.net.xml;.rou.xml;.poly.xml:
请根据文件位置修改文件路径!
- import numpy as np
- import subprocess
-
- #生成map.net.xml
-
- # 设置你的.osm文件路径
- osm_file_path = 'costco.osm' #根据https://www.openstreetmap.org 网站导出的
-
- # 设置输出文件路径
- output_file_path = 'costco.net.xml'
-
- # 构建netconvert命令
- netconvert_command = f'netconvert --osm-files {osm_file_path} -o {output_file_path}'
-
- # 调用命令
- subprocess.run(netconvert_command, shell=True)
- print(".net.xml成功生成")
-
- #生成map.poly.xml
-
- osm_file_path = 'costco.osm' #根据https://www.openstreetmap.org 网站导出的
- type_file_path = 'typemap.xml' #内容根据CSDN收藏
-
- # 设置输出文件路径
- output_file_path = 'costco.poly.xml'
-
- # 构建netconvert命令
- netconvert_command = f'polyconvert --net-file costco.net.xml --osm-files {osm_file_path} --type-file {type_file_path} -o {output_file_path}'
-
- # 调用命令
- subprocess.run(netconvert_command, shell=True)
- print(".poly.xml成功生成")
-
- # 生成map.rou.xml
-
- # 设置SUMO环境变量(请根据你的SUMO安装路径进行调整)
- sumo_tools_dir = "C:/Users/Majiaming/Desktop/WESTERN/9038_wireless_comm/project/project_new/sumo-1.10.0/tools"
- sumo_network_file = "costco.net.xml"
- output_route_file = "costco.rou.xml"
-
- # 构建randomTrips.py脚本的完整命令
- command = [
- "python",
- f"{sumo_tools_dir}/randomTrips.py",
- "-n", sumo_network_file,
- "-e", "100",
- "-l",
- ]
-
- # 使用subprocess运行命令
- result = subprocess.run(command, capture_output=True, text=True)
-
- # 检查命令输出(可选)
- if result.returncode == 0:
- print("执行成功1")
- print(result.stdout) # 打印标准输出
- else:
- print("执行出错1")
- print(result.stderr) # 打印错误输出
-
- ####################################################
-
- # 构建randomTrips.py脚本的完整命令
- command = [
- "python",
- f"{sumo_tools_dir}/randomTrips.py",
- "-n", sumo_network_file,
- "-r", output_route_file,
- "-e", "50",
- "-l",
- ]
-
- # 使用subprocess运行命令
- result = subprocess.run(command, capture_output=True, text=True)
-
- # 检查命令输出(可选)
- if result.returncode == 0:
- print("执行成功2")
- print(".rou.xml成功生成")
- print(result.stdout) # 打印标准输出
- else:
- print("执行出错2")
- print(result.stderr) # 打印错误输出
③ 生成后,将这三个文件放到“my_veins_inet”下备用
“my_veins_inet”下已经有了sumo相关的各种文件,现在需要根据具体要求修改它们
① 将square.sumocfg和square.launchd.xml改名为costco.sumocfg和costco.launchd.xml,并根据新的rou, net和poly文件修改内容:
修改完成后,可以用sumo gui打开costco.sumocfg查看效果:
② 修改.rou.xml:
为了让仿真效果尽可能的清晰,将随机生成的50辆车的代码注释掉,并替换成如下的代码:
- <routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/routes_file.xsd">
- <vType id="vtype0" accel="2.6" decel="4.5" sigma="0.5" length="4.5" minGap="2.5" maxSpeed="14" color="1,1,0"/>
- <route id="route0" edges="-62088703#4 -62088703#3 -62088703#2 -62088703#1 474843229#0 474843229#1 474843229#2 474843229#3 474843229#4 474843229#5 474843229#6 466961753#1 466961753#2 466961753#3 466961753#4 466961753#5 466961753#6 466961753#7 466961753#8 -466961753#8"/>
- <flow id="flow0" type="vtype0" route="route0" begin="0" period="3" number="5" arrivalPos="0" />
- </routes>
<vType>
元素:
- 定义了一种车辆类型
vtype0
accel
(加速度): 车辆的最大加速度,这里是2.6米/秒²decel
(减速度): 车辆的最大减速度,这里是4.5米/秒²sigma
(驾驶员不确定性): 描述了驾驶员行为的不确定性,这里是0.5,范围从0(完全确定行为)到1(非常不确定的行为)length
(长度): 车辆的长度,这里是4.5米minGap
(最小间隙): 车辆之间的最小间隙,这里是2.5米maxSpeed
(最大速度): 车辆的最大速度,这里是14米/秒color
(颜色): 车辆的颜色,这里定义为黄色(RGB颜色模式中的1,1,0)
<route>
元素:
- 定义了一个路线
route0
id
属性指定了路线的唯一标识符edges
属性列出了构成该路线的边的序列。边是道路网中的基本元素,代表单向的道路。这里列出的边通过它们的唯一标识符来指定,负号表示逆向行驶
<flow>
元素:
- 定义了一个流量
flow0
,表示一定数量的车辆将沿指定的路线移动id
属性为流量提供了一个唯一标识type
属性指定了车辆类型,这里引用了之前定义的vtype0
route
属性指定了车辆将遵循的路线,这里是route0
begin
属性定义了流量开始的时间(秒),这里是从仿真开始的0秒period
属性定义了车辆生成的时间间隔(秒),这里是每3秒生成一次number
属性定义了将被生成的车辆总数,这里是5辆arrivalPos
属性定义了车辆到达目的地的位置,这里是0,通常表示车辆将尝试行驶整个路线
① 修改Scenario.ned:
修改灰色区域范围大小,以适应新的sumo地图
② 修改omnetpp.ini:
① 在veins_inet -> src -> veins_inet 下分别创建“MYApplication.cc”;“MYApplication.h”和“MYApplication.ned”:
- #include "veins_inet/MYApplication.h"
-
- #include "inet/common/ModuleAccess.h"
- #include "inet/common/packet/Packet.h"
- #include "inet/common/TagBase_m.h"
- #include "inet/common/TimeTag_m.h"
- #include "inet/networklayer/common/L3AddressResolver.h"
- #include "inet/networklayer/common/L3AddressTag_m.h"
- #include "inet/transportlayer/contract/udp/UdpControlInfo_m.h"
-
- #include "veins_inet/VeinsInetSampleMessage_m.h"
-
- #include <cstdlib> // 引入标准库以使用rand()和srand()
- #include <ctime> // 引入时间库以初始化随机数种子
-
- using namespace inet;
-
- Define_Module(MYApplication);
-
- MYApplication::MYApplication()
- {
- }
-
- /* 在5辆车中随机模拟两辆车事故并发送事故包
- bool MYApplication::startApplication()
- {
- int v_num = 0;
- int index1 = 0;
- int index2 = 0;
- // 初始化随机数种子
- srand(static_cast<unsigned>(time(nullptr)));
- v_num = 5;
- if (v_num > 1) { // 确保至少有两个模块可供选择
- // 生成两个小于v_num的随机数
- index1 = rand() % v_num;
- index2 = rand() % v_num;
- while (index1 == index2) { // 确保两个随机数不相同
- index2 = rand() % v_num;
- }
- // 对于选中的两个随机索引执行操作
- std::vector<int> selectedIndexes = {index1, index2};
- for (int index : selectedIndexes) {
- if (getParentModule()->getIndex() == index) {
- auto callback = [this]() {
- getParentModule()->getDisplayString().setTagArg("i", 1, "red");
- traciVehicle->setSpeed(0);
- auto payload = makeShared<VeinsInetSampleMessage>();
- payload->setChunkLength(B(100));
- payload->setRoadId(traciVehicle->getRoadId().c_str());
- timestampPayload(payload);
- auto packet = createPacket("accident");
- packet->insertAtBack(payload);
- sendPacket(std::move(packet));
- auto resumeCallback = [this]() {
- traciVehicle->setSpeed(-1); // 车辆恢复正常
- };
- timerManager.create(veins::TimerSpecification(resumeCallback).oneshotIn(SimTime(30, SIMTIME_S)));
- };
- timerManager.create(veins::TimerSpecification(callback).oneshotAt(SimTime(20, SIMTIME_S)));
- }
- }
- }
- return true;
- }
- */
-
- /* 在5辆车中使第一辆车发送事故并发送事故包 */
- bool MYApplication::startApplication()
- {
- // host[0] should stop at t=20s
- if (getParentModule()->getIndex() == 0) { //如果节点为0
- auto callback = [this]() { //这个函数在20秒倒计时结束后被运行
- getParentModule()->getDisplayString().setTagArg("i", 1, "red"); //车子变红
-
- traciVehicle->setSpeed(0); //车子不会立刻停下来,因为仿真软件中定义的物理限制的影响,例如车辆的减速能力,但车子肯定会在20秒后很短时间内停下
-
- //定义要发送的包的内容
- auto payload = makeShared<VeinsInetSampleMessage>();
- payload->setChunkLength(B(100));
- payload->setRoadId(traciVehicle->getRoadId().c_str());
- timestampPayload(payload);
-
- //定义包的名字并发送包
- auto packet = createPacket("accident");
- packet->insertAtBack(payload);
- sendPacket(std::move(packet));
-
- // host should continue after 30s
- auto callback = [this]() { //这个函数在30秒倒计时结束后被运行
- traciVehicle->setSpeed(-1); //车子恢复正常
- };
- timerManager.create(veins::TimerSpecification(callback).oneshotIn(SimTime(30, SIMTIME_S)));//30秒倒计时
- };
- timerManager.create(veins::TimerSpecification(callback).oneshotAt(SimTime(20, SIMTIME_S)));//20秒倒计时
- }
-
- return true;
- }
-
-
-
- bool MYApplication::stopApplication()
- {
- return true;
- }
-
- MYApplication::~MYApplication()
- {
- }
-
- void MYApplication::processPacket(std::shared_ptr<inet::Packet> pk) // 定义处理数据包的函数,参数是一个指向数据包的智能指针
- {
- auto payload = pk->peekAtFront<VeinsInetSampleMessage>(); // 从数据包中提取出负载数据,假设为VeinsInetSampleMessage类型
-
- EV_INFO << "Received packet: " << payload << endl; // 打印接收到的数据包信息到日志
-
- getParentModule()->getDisplayString().setTagArg("i", 1, "green"); // 改变接收到数据包的车辆显示为绿色
-
- traciVehicle->changeRoute(payload->getRoadId(), 999.9); // 根据数据包内容改变车辆的行驶路线
-
- if (haveForwarded) return; // 如果该数据包已经被转发过,则直接返回,不进行下一步处理
-
- // 如果是第一次收到该消息
- auto packet = createPacket("relay"); // 创建一个新的数据包,用于转发
- packet->insertAtBack(payload); // 在新数据包的末尾插入收到的负载数据
- sendPacket(std::move(packet)); // 转发这个数据包
-
- haveForwarded = true; // 标记该数据包已被处理并转发
- }
- #pragma once // 确保头文件只被包含一次,避免重复包含导致的编译错误
-
- #include "veins_inet/veins_inet.h" // 包含veins_inet相关的头文件
-
- #include "veins_inet/VeinsInetApplicationBase.h" // 包含VeinsInetApplicationBase的头文件,这是基类的头文件
-
- class VEINS_INET_API MYApplication : public veins::VeinsInetApplicationBase { // 定义一个MYApplication类,继承自VeinsInetApplicationBase
- protected:
- bool haveForwarded = false; // 保护成员变量,用来标记是否已经转发过数据包
-
- protected:
- virtual bool startApplication() override; // 覆盖基类的startApplication方法,用于启动应用
- virtual bool stopApplication() override; // 覆盖基类的stopApplication方法,用于停止应用
- virtual void processPacket(std::shared_ptr<inet::Packet> pk) override; // 覆盖基类的processPacket方法,用于处理接收到的数据包
-
- public:
- MYApplication(); // 构造函数
- ~MYApplication(); // 析构函数
- };
- package veins_inet.src.veins_inet; // 定义了代码所在的包名
-
- import veins_inet.src.veins_inet.VeinsInetApplicationBase; // 导入了VeinsInetApplicationBase类
-
- simple MYApplication extends VeinsInetApplicationBase // 定义一个简单的模块MYApplication,它继承自VeinsInetApplicationBase
- {
- parameters:
- @class(MYApplication); // 指定了MYApplication模块的C++实现类是MYApplication
- gates:
- // 这里可以定义模块的门(gates),用于连接模块之间的消息传递,目前留空
- }
代码规定了以下仿真的逻辑:
当node[0]停止,即模拟事故发生时,会立刻装载一个大小为100Byte的包,并在其中添加当前所在路的ID,然后向通讯范围内的所有节点(汽车)发送这个包。每个收到这个包的节点(汽车)会立刻从包中获取事故路段的ID,并假设事故路段的通过时间为最大。这就导致所有会经过事故路段且拥有切换路径条件的汽车在收到这个包后会立刻切换路径。同时,向其通信范围的所有节点(汽车)转发一次包的内容。
② 再次修改.ini文件,将应用层协议换成“MYApplication”:(别忘!)
① 右击omnetpp.ini,右击Runs as -> Run Configuration:
② 设置完成后点击右下角:Apply -> run:
最终仿真效果:
上图显示了4个节点随着时间的吞吐量变化,可以明显的看到,对于这4个节点,吞吐量都在事故发送时开始增加,在0.1秒左右达到最高,并在0.2秒后快速下降,可见通讯速度很快。
可见,node0为2次,其他三个节点都为1次,这也很好理解,因为node0是事故发送的节点,所以需要发送一次事故包一次转发包;而其他节点只需要发送一次转发包即可。
现在,已经实现OMNET++,SUMO,VEINS和INET的联合仿真,但是背后的原理是什么其实依然一知半解,于是我开始解读代码,尝试理解为什么仿真会这样进行。
注意,以下所有内容都属于自问自答,完全可能有错误,欢迎大家指正,本人纯小白
Q1:在最后的仿真路径中,Scenario.ned中的manager,,radio medium,node和physical environmnet分别到底代表什么?在图形界面中拖动他们的位置或改变他们的大小有什么实际的意义?
A:
manager
通常用于仿真管理任务,如控制仿真流程、管理节点间的通信、协调事件等radioMedium
定义了无线电传播的模型,如何模拟信号的传播和衰减、如何处理节点间的无线通信等node
代表仿真网络中的一个参与者或设备,在车辆网络仿真中,这些节点通常是车辆physicalEnvironment
模块定义了仿真的物理环境,包括地形、建筑物等,这些因素可能会影响信号的传播和车辆的移动在OMNeT++ IDE的图形界面中,拖动这些组件的位置或改变它们的大小主要是为了改善视觉布局,帮助仿真设计者更好地理解和组织仿真场景。这些操作不直接影响仿真逻辑或结果,仿真的逻辑和行为完全由NED文件中的参数定义和配置文件(
.ini
文件)中的设置决定。
Q2:根据网络原理,网络协议应该分为5层,应用层,传输层,网络层,链路层,物理层,这些体现在项目的哪里?
A:
- 在这个仿真中,每一个节点(汽车)都被注册为一个名为VeinsInetCar的对象:
- 而通过跳转VeinsInetCar.nedf会发现,它继承自一个叫AdhocHost的对象:
- 使用同样的方法不停的跳转,会得出这样一个继承的关系:
- 而通过分析这些.ned文件,可以总结出以下内容:
- 所以,每个汽车所代表的VeinsInetCar都是从NodeBase一路继承过来的,而观察继承的路线就可以发现,在一路的继承中,就分别实现了网络协议中5大层的接口定义!
Q3:根据Q2,有个随之而来的问题:我理解了为什么VeinsInetCar具备了完整的协议栈,网络协议的5大层实现在了哪里。但是我依然不理解:从NodeBase一直继承到了VeinsInetCar,现在已经有了无数的接口用来配置完整的协议,但是使用哪些接口具体配置哪些值,比如在链路层具体配置为无线还是VLAN,网络层具体配置为IPV4还是IPV6,传输层具体配置为TCP还是UDP,这些具体的配置在哪里?
A:
答案是:大部分在仿真文件omnetpp.ini中定义:
- 链路层具体配置:
- 网络层具体配置:
- 应用层具体配置:
物理层没有定义很好理解,这都是最底层的设定。
但是,显然会有一个问题,为什么没有传输层的相关定义?到底使用TCP还是UDP还是什么?
chatgpt给出了三种回答:
默认配置:许多OMNeT++和INET模块,包括传输层协议模块,具有默认配置。例如,如果一个应用需要使用UDP或TCP,并且在其模块定义中正确指定了,那么即使在
.ini
文件中没有明确配置,这些传输层协议也会被自动实例化并使用默认设置。应用层决定:在许多情况下,特别是在使用简单应用(如
UdpBasicApp
)或其他特定的应用模型时,所使用的传输协议(UDP、TCP等)可能已在应用层模块的实现中明确定义。例如,一些应用模型默认使用UDP进行通信,而无需在.ini
文件中进行额外配置。模块继承:由于
VeinsInetCar
继承自AdhocHost
,进而继承自StandardHost
,一直到NodeBase
,这些基础模块可能已经包含了对传输层协议的支持。特别是在AdhocHost
或StandardHost
级别,通常会包括对主要传输层协议(如TCP和UDP)的支持,而无需在每个仿真场景的.ini
文件中进行单独配置。我认为都有一定的道理,根据上面的截图可以知道,应用层是由VeinsInetSampleApplication来实现的,跳转到其对应的.ned文件,可以发现它继承自VeinsInetApplicationBase:
再次跳转,找到这个VeinsInetApplicationBase:
这个代码并没有明确的规定UDP还是TCP,但是根据socket门的注释,应该是使用的UDP。
- 最后,为了验证,运行仿真并点击一个节点:
可见,虽然链路层出现了循环回路lo,传输层出现了tcp,但是真正实现从上到下沟通的协议还分别是:UDP,IPV4和WLAN
(我的推测是:由于没有明确的规定lo和tcp不能使用,所以他们也可能存在,并在某种方面帮助消息传播?)
Q4:
A:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。