This is a companion piece to this video
If you want the terminal and IDE on screen, watch that. If you want the reference doc you can copy commands out of, this is that doc.
What opp_env actually installs
One command, from the setup video:
opp_env install -w vanet-workspace --init veins-latestThat resolves to three directories, siblings at the top level of the workspace:
inet-4.6.0/
omnetpp-6.3.0/
veins-5.3.1/INET shows up even though nothing here asked for it directly. Veins borrows some of its channel and mobility models. There’s no veins_inet folder. That component is for a different install combination entirely, one that pairs Veins with INET’s full networking stack. A plain veins-latest doesn’t need it.
Check the resolved dependency graph yourself instead of trusting a folder name:
opp_env info veins-latestEntering the workspace
Everything opp_env installs is only usable inside its own shell:
opp_env shell
omnetppThe IDE opens already pointed at the workspace. No manual “Switch Workspace,” no manual project import in most cases, since recent OMNeT++ versions auto import on launch.
Creating a project
File, New, OMNeT++ Project. Leave “Use default location” checked. It already resolves to a sibling of the three installed directories, since the IDE’s workspace root is the workspace itself.
Keep the standard split: src/ for C++ modules, simulations/ for network definitions and run configs.
Two references need to be set, and they are not the same thing.
Project References (right click project, Properties, Project References) resolve NED types. Check inet-4.6.0 and veins-5.3.1 here.
Makemake settings (Properties, OMNeT++, Makemake, select src, Options, Compile tab) resolve C++ includes. Confirm veins is checked here too, separately.
Missing the second one is the most common failure at this stage. The NED import resolves clean. The header still isn’t found. Two different resolution mechanisms, easy to conflate.
A minimal SUMO network
Everything here lives flat inside simulations/. No subfolders. veins_launchd resolves file names relative to that directory and refuses anything containing a slash, a security measure against reaching outside it.
Generate a small grid:
netgenerate --grid --grid.number=2 -o simple.net.xmlRoute file, simple.rou.xml:
<routes>
<vType id="car" accel="2.6" decel="4.5" length="5" maxSpeed="20"/>
<route id="route0" edges="A0A1 A1B1"/>
<vehicle id="veh0" type="car" route="route0" depart="0"/>
<vehicle id="veh1" type="car" route="route0" depart="5"/>
</routes>SUMO config, simple.sumo.cfg:
<configuration>
<input>
<net-file value="simple.net.xml"/>
<route-files value="simple.rou.xml"/>
</input>
</configuration>Launch config, simple.launchd.xml, the file the daemon actually reads:
<launch>
<copy file="simple.net.xml" />
<copy file="simple.rou.xml" />
<copy file="simple.sumo.cfg" type="config" />
</launch>Copy config.xml and antenna.xml from the bundled Veins example rather than writing your own. They describe the propagation model, decider, and antenna pattern. Nobody hand writes radio physics for a test scenario.
A custom app layer
PingApp.h:
#pragma once
#include "veins/modules/application/ieee80211p/DemoBaseApplLayer.h"
using namespace veins;
class PingApp : public DemoBaseApplLayer {
protected:
void initialize(int stage) override;
void handleSelfMsg(cMessage* msg) override;
void onWSM(BaseFrame1609_4* wsm) override;
};PingApp.ned:
simple PingApp extends org.car2x.veins.modules.application.ieee80211p.DemoBaseApplLayer
{
parameters:
@class(PingApp);
}The @class line is not optional decoration. Without it, the NED type inherits the parent’s class binding and the simulation tries to instantiate DemoBaseApplLayer directly, a base class that was never registered to run on its own.
PingApp.cc:
#include "PingApp.h"
Define_Module(PingApp);
void PingApp::initialize(int stage)
{
DemoBaseApplLayer::initialize(stage);
if (stage == 0) {
scheduleAt(simTime() + 2, new cMessage("sendPing"));
}
}
void PingApp::handleSelfMsg(cMessage* msg)
{
if (strcmp(msg->getName(), "sendPing") == 0) {
BaseFrame1609_4* wsm = new BaseFrame1609_4();
populateWSM(wsm);
sendDown(wsm);
delete msg;
}
else {
DemoBaseApplLayer::handleSelfMsg(msg);
}
}
void PingApp::onWSM(BaseFrame1609_4* wsm)
{
EV << "Received a message at " << simTime() << "\n";
}Two seconds after start, each vehicle builds a message, fills it in with populateWSM, and sends it with sendDown. Whichever vehicle is in range receives it through onWSM.
Worth flagging since it cost real debugging time: DemoBaseApplLayer splits beacons and data messages into separate callbacks. Beacons land in onBSM. This code path deliberately skips the built in beacon mechanism entirely and sends its own message instead, which is why onWSM is the right hook here and sendBeacons stays false in the ini.
The network
PingNetwork.ned:
package vanet_scenarios.simulations;
import org.car2x.veins.nodes.Scenario;
network PingNetwork extends Scenario
{
}No car submodule. Vehicles are created at runtime by TraCIScenarioManager as SUMO reports them, and that same process wires each vehicle’s radio gate into the connection manager. A hand declared car submodule skips that wiring and leaves the radio gate unconnected, an error worth avoiding rather than debugging.
The full ini
[General]
cmdenv-express-mode = true
cmdenv-autoflush = true
cmdenv-status-frequency = 1s
**.cmdenv-log-level = info
network = PingNetwork
sim-time-limit = 30s
**.scalar-recording = true
**.vector-recording = true
*.playgroundSizeX = 200m
*.playgroundSizeY = 200m
*.playgroundSizeZ = 50m
*.annotations.draw = true
*.manager.updateInterval = 1s
*.manager.host = "localhost"
*.manager.port = 9999
*.manager.autoShutdown = true
*.manager.launchConfig = xmldoc("simple.launchd.xml")
*.connectionManager.sendDirect = true
*.connectionManager.maxInterfDist = 2600m
*.connectionManager.drawMaxIntfDist = false
*.**.nic.mac1609_4.useServiceChannel = false
*.**.nic.mac1609_4.txPower = 20mW
*.**.nic.mac1609_4.bitrate = 6Mbps
*.**.nic.phy80211p.minPowerLevel = -110dBm
*.**.nic.phy80211p.useNoiseFloor = true
*.**.nic.phy80211p.noiseFloor = -98dBm
*.**.nic.phy80211p.decider = xmldoc("config.xml")
*.**.nic.phy80211p.analogueModels = xmldoc("config.xml")
*.**.nic.phy80211p.usePropagationDelay = true
*.**.nic.phy80211p.antenna = xmldoc("antenna.xml", "/root/Antenna[@id='monopole']")
*.node[*].nic.phy80211p.antennaOffsetY = 0 m
*.node[*].nic.phy80211p.antennaOffsetZ = 1.895 m
*.node[*].applType = "PingApp"
*.node[*].appl.headerLength = 80 bit
*.node[*].appl.sendBeacons = false
*.node[*].appl.dataOnSch = false
*.node[*].veinsmobility.x = 0
*.node[*].veinsmobility.y = 0
*.node[*].veinsmobility.z = 0
*.node[*].veinsmobility.setHostSpeed = falseWhat this proves, and what it doesn’t
Two vehicles moving under TraCI control, a message you scheduled and sent yourself, received by the other vehicle through Veins, landing in code you wrote. That’s the full chain, end to end, minimal as it gets.
It doesn’t prove anything about realistic road networks, traffic density, or actual application logic. That’s next.
Next up
Real VANET scenario, OpenStreetMap road data instead of a synthetic grid, a roadside unit, and results worth measuring. Subscribe if you want it when it lands.


