Veins教程07 - Veins魔改应用层

Modification on Veins

这篇实现V2R通信,目标是RSU能知晓有多少小车可以通信,并借此深入了解Message,Application应用层和mac层的相关知识。讲解两个版本,第一个是,小车间歇发送报告消息,RSU接收并记录。第二个版本是RSU定时间歇发送询问信息,小车收到后应答,RSU接收并记录。

Version One

Message

首先自定义一个Message,在src/veins/modules/messages下新建一个名为ReportMessage.msg的文件,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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的顺序修改项目

Car - TraCIDemo11p

接下来修改小车的实现文件使之满足要求。Veins中自带的src/veins/modules/application/traci/TraCIDemo11p.*就是小车的简易实现,准确说是其应用层的实现,本篇只完成对其功能的修改来更好地理解Veins,之后如有需要可以自定义整个文件。

打开TraCIDemo11p.ned可知其继承自DemoBaseApplLayer,显然后者也是一个简易实现的应用层。

应用层的介绍可以参考Veins教程06 - Veins应用层详解

打开TraCIDemo11p.h可知其只实现了onWSMonWSAhandleSelfMsghandlePositionUpdate四个函数。前两个是小车分别收到WSM(WAVE Short Message)和WSA(WAVE Service Advertisment)消息时作出的处理。

handlePositionUpdate是每次小车更新位置时的函数处理。“小车更新位置”的时刻就是每次SUMO回传小车的仿真结果的时刻。

handleSelfMsg其实是一个OMNet++的概念。通俗来说就是处理小车发送给自己的消息。小车给自己发送消息用到的是OMNet++提供的scheduleAt函数,scheduleAt函数相当于一个Timer函数,类似Node.js中的setTimeOut,也就是在设定的时刻给自发送一个消息,并因此触发handleSelfMsg

在这个版本中,小车并不需要接收我们自定的ReportMessage,因此我们只需要实现小车不断发送ReportMessage的功能

Editing handlePositionUpdate

最简单的方式是在小车每次更新坐标时,也就是handlePositionUpdate时发送。可以在该函数中的if前加上:

1
2
3
4
ReportMessage* rm = new ReportMessage();
populateWSM(rm);
rm->setSenderPos(curPosition);
sendDown(rm);

记得include对应的文件

populateWSM函数是一个统一处理消息的函数,简便起见,这里就直接使用了

curPositionDemoBaseApplLayer中的成员变量,记录的是当前坐标

sendDown函数是往下层也就是mac层传递消息,可以直接理解成发送。深入查看的话mac层会做一个消息类型的记录

if内的功能是判断小车是否停止不动长达十秒,来一次判断是否有阻塞/车祸发生。如果有,则传递消息并reroute,如内置demo中演示的效果。

Set Timer

也可以在initialize初始化函数中通过scheduleAt函数设定一个Timer

需要注意的是,initialize接受一个stage参数,可以实现分阶段初始化,因此在修改initialize时,需要避免在多个stageinitialize中重复设置。可以参考如下实现:

1
2
3
4
5
6
if (stage == 1) {
ReportMessage* rm = new ReportMessage();
populateWSM(rm);
rm->setSenderAddress(myId);
scheduleAt(simTime() + uniform(0.01, 0.2), rm);
}

同时,由于scheduleAt会在设定时刻给自己发送消息,还需要在handleSelfMsg添加:

1
2
3
4
5
else if (ReportMessage* rm = dynamic_cast<ReportMessage*>(msg)) {
rm->setSenderPos(curPosition);
sendDown(rm->dup());
scheduleAt(simTime() + 1, rm);
}

RSU - TraCIDemoRSU11p

这个版本中RSU只负责接收ReportMessage,因此只需要添加onRM(ReportMessage* frame)函数处理ReportMessage消息。

不过由于需要统计可连接的小车数量,需要添加一个成员变量std::set<LAddress::L2Type> connectedNodes

L2Typeveins/base/utils/SimpleAddress中定义的地址(ID)类型。

TraCIDemoRSU11p.h可以参考修改为:

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
#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文件参考添加

1
2
3
4
5
6
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

1
2
3
4
5
6
7
8
9
10
11
12
13
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"));
}

DemoBaseApplLayer

主要需要修改:

  • populateWSM: 可以新建一个ReportMessage分支,不过默认分支已经足够
  • handleLowerMsg: 这个是接收消息的分发处理函数,需要新建一个ReportMessage分支,这样RSUc才能正确收到消息

参考如下修改

1
2
3
4
5
6
7
void DemoBaseApplLayer::populateWSM(BaseFrame1609_4* wsm, LAddress::L2Type rcvId, int serial)
{
else if (ReportMessage* rm = dynamic_cast<ReportMessage*>(wsm)) {
receivedRMs++;
onRM(rm);
}
}

其他有些不太重要的地方可以参考github代码

Version Two

主要的修改思路与Version One类似,RSU中设置Timer不断发送轮训消息,小车收到后回传给RSU。如果不想将发送和回传的消息分开,可以都使用ReportMessage,但是需要设置一个类似于消息类型的变量。不然小车收到其他小车发送的回传ReportMessage后也会‘回传’,造成混乱。

评论