Veins ReportMsg - SpereShelde/Veins GitHub Wiki
这篇实现V2R通信,目标是RSU能知晓有多少小车可以通信,并借此深入了解Message,Application应用层和mac层的相关知识。讲解两个版本,第一个是,小车间歇发送报告消息,RSU接收并记录。第二个版本是RSU定时间歇发送询问信息,小车收到后应答,RSU接收并记录。
首先自定义一个Message,在src/veins/modules/messages
下新建一个名为ReportMessage.msg
的文件,实现如下:
cplusplus {{
#include "veins/base/utils/Coord.h"
#include "veins/modules/messages/BaseFrame1609_4_m.h"
#include "veins/base/utils/SimpleAddress.h"
}}
namespace veins;
class BaseFrame1609_4;
class noncobject Coord;
class LAddress::L2Type extends void;
packet ReportMessage extends BaseFrame1609_4 {
Coord senderPos;
LAddress::L2Type senderAddress = -1;
}
ReportMessage继承了BaseFrame1609_4并包含两个属性分别是Coord
类型的发送者所标,以及L2Type
的发送者ID。编译之后同目录下生成同名.h
和.cc
文件。其函数实现暂时不做修改,默认的setter和getter足够使用。BaseFrame为最基础的消息帧,只包含几个基础属性,便于自定义。
接下来以TraCIDemoXX
-> DemoBaseApplLayer
的顺序修改项目
接下来修改小车的实现文件使之满足要求。Veins中自带的src/veins/modules/application/traci/TraCIDemo11p.*
就是小车的简易实现,准确说是其应用层的实现,本篇只完成对其功能的修改来更好地理解Veins,之后如有需要可以自定义整个文件。
打开TraCIDemo11p.ned
可知其继承自DemoBaseApplLayer
,显然后者也是一个简易实现的应用层。
应用层的介绍可以参考Verins-Appl
打开TraCIDemo11p.h
可知其只实现了onWSM
,onWSA
,handleSelfMsg
,handlePositionUpdate
四个函数。前两个是小车分别收到WSM
(WAVE Short Message)和WSA
(WAVE Service Advertisment)消息时作出的处理。
handlePositionUpdate
是每次小车更新位置时的函数处理。“小车更新位置”的时刻就是每次SUMO回传小车的仿真结果的时刻。
handleSelfMsg
其实是一个OMNet++的概念。通俗来说就是处理小车发送给自己的消息。小车给自己发送消息用到的是OMNet++提供的scheduleAt
函数,scheduleAt
函数相当于一个Timer函数,类似Node.js中的setTimeOut
,也就是在设定的时刻给自发送一个消息,并因此触发handleSelfMsg
。
在这个版本中,小车并不需要接收我们自定的ReportMessage
,因此我们只需要实现小车不断发送ReportMessage
的功能
最简单的方式是在小车每次更新坐标时,也就是handlePositionUpdate
时发送。可以在该函数中的if
前加上:
ReportMessage* rm = new ReportMessage();
populateWSM(rm);
rm->setSenderPos(curPosition);
sendDown(rm);
记得include对应的文件
populateWSM
函数是一个统一处理消息的函数,简便起见,这里就直接使用了
curPosition
是DemoBaseApplLayer
中的成员变量,记录的是当前坐标
sendDown
函数是往下层也就是mac层传递消息,可以直接理解成发送。深入查看的话mac层会做一个消息类型的记录
if
内的功能是判断小车是否停止不动长达十秒,来一次判断是否有阻塞/车祸发生。如果有,则传递消息并reroute,如内置demo中演示的效果。
也可以在initialize
初始化函数中通过scheduleAt
函数设定一个Timer
需要注意的是,initialize
接受一个stage
参数,可以实现分阶段初始化,因此在修改initialize
时,需要避免在多个stage
的initialize
中重复设置。可以参考如下实现:
if (stage == 1) {
ReportMessage* rm = new ReportMessage();
populateWSM(rm);
rm->setSenderAddress(myId);
scheduleAt(simTime() + uniform(0.01, 0.2), rm);
}
同时,由于scheduleAt
会在设定时刻给自己发送消息,还需要在handleSelfMsg
添加:
else if (ReportMessage* rm = dynamic_cast<ReportMessage*>(msg)) {
rm->setSenderPos(curPosition);
sendDown(rm->dup());
scheduleAt(simTime() + 1, rm);
}
这个版本中RSU只负责接收ReportMessage
,因此只需要添加onRM(ReportMessage* frame)
函数处理ReportMessage
消息。
不过由于需要统计可连接的小车数量,需要添加一个成员变量std::set<LAddress::L2Type> connectedNodes
L2Type
是veins/base/utils/SimpleAddress
中定义的地址(ID)类型。
TraCIDemoRSU11p.h
可以参考修改为:
#pragma once
#include "veins/modules/application/ieee80211p/DemoBaseApplLayer.h"
#include "veins/base/utils/SimpleAddress.h"
namespace veins {
/**
* Small RSU Demo using 11p
*/
class VEINS_API TraCIDemoRSU11p : public DemoBaseApplLayer {
public:
void initialize(int stage) override;
protected:
std::set<LAddress::L2Type> connectedNodes;
protected:
void onWSM(BaseFrame1609_4* wsm) override;
void onWSA(DemoServiceAdvertisment* wsa) override;
void onRM(ReportMessage* rm) override;
void handleSelfMsg(cMessage* msg) override;
};
} // namespace veins
对应的TraCIDemoRSU11p.cc
文件参考添加
void TraCIDemoRSU11p::onRM(ReportMessage* frame)
{
ReportMessage* rm = check_and_cast<ReportMessage*>(frame);
std::cout << "Node " << rm->getSenderAddress() << " reported from position " << rm->getSenderPos() << std::endl;
connectedNodes.insert(rm->getSenderAddress());
}
此时RSU已经可以接收并统计小车
不过如果想要输出,可以参考设置Timer
void TraCIDemoRSU11p::initialize(int stage)
{
DemoBaseApplLayer::initialize(stage);
if (stage == 0) {
scheduleAt(simTime() + uniform(0.01, 0.2), new cMessage("Connected nodes"));
}
}
void TraCIDemoRSU11p::handleSelfMsg(cMessage* msg)
{
std::cout << "There are " << connectedNodes.size() << " connected nodes in the range of RSU " << myId << std::endl;
scheduleAt(simTime() + 2, new cMessage("Connected nodes"));
}
主要需要修改:
- populateWSM: 可以新建一个
ReportMessage
分支,不过默认分支已经足够 - handleLowerMsg: 这个是接收消息的分发处理函数,需要新建一个
ReportMessage
分支,这样RSUc才能正确收到消息
参考如下修改
void DemoBaseApplLayer::populateWSM(BaseFrame1609_4* wsm, LAddress::L2Type rcvId, int serial)
{
else if (ReportMessage* rm = dynamic_cast<ReportMessage*>(wsm)) {
receivedRMs++;
onRM(rm);
}
}
主要的修改思路与Version One类似,RSU中设置Timer不断发送轮训消息,小车收到后回传给RSU。如果不想将发送和回传的消息分开,可以都使用ReportMessage
,但是需要设置一个类似于消息类型的变量。不然小车收到其他小车发送的回传ReportMessage
后也会‘回传’,造成混乱。
其中src
目下为Version One; src1
为Version Two
问为什么每次都要scheduleAt
而不是直接sendDown
?
一方面是可以实现Timer,另一方面是避免信道冲突,造成信号无法被接收,通过scheduleAt
设置随机延时可以有效避免,但也不是一定避免,之后会介绍解决办法。